Learning from your shell history
Whenever you use your shell (Bash, Zsh, Fish, Powershell, Nushell, etc.), each command you run is saved in a history file.
It’s handy to re-run a similar command in the future thanks to Ctrl-R
, but
you can also use to as a source of information to determine which part of your
workflow you could improve.
1. Anatomy of a command
A command usually looks like this:
command argument_0 argument_1 <argument_n+1>
Looking at the history of commands, I want to see which command
I run the most
often, but also see which arguments
I use them with the most.
2. Example
I wrote a small program that generates an html file that shows how many times a command has been run, with a collapsible list of arguments it was used with:
It basically looks like this (click on each line to expand that section):
166 - podman
-
build --format docker --arch amd64 .
-
build --format docker --arch amd64 .
-
container list
-
inspect docker.io/caddy/caddy:latest
-
(etc.)
154 - cd
-
emtpy
-
empty
-
~/dev
-
~/dev
-
~/dev
-
~/dev
-
~/dev
-
-
-
(etc.)
48 - ls
-
emtpy
-
empty
-
empty
-
-l
-
(etc.)
47 - jq
-
'' ./v.json
-
'. | @csv' ./values.json
-
--argjson i '2' '.perfCampaigns[$i]' ./v.json
-
(etc.)
In my case, I saw that I often use cd
with very similar arguments,
and with a limited set of directories.
I could probably create an alias for cd
in conjunction with fzf
to go
to where I often go, without relying on tab auto-completion and speed things up.
Looking at my podman
section, I can also see that I should look into some aliases
as well.
3. How to generate the report and limitations
This analysis only helps for commands that start with the same keyword, but it does not help with commands in the middle of a pipe.
If you want to try this, save the following code block as History.java
(java 17+).
Then, run: javac History.java && java History .bash_history > output.html
Finally, open output.html
in your browser.
Feel free to replace .bash_history
by the history file of your shell (only
tested with Bash, Fish and Powershell history file.)
import java.util.*;
import java.nio.file.*;
import java.io.IOException;
record Command(String command, List<String> args) {}
class History {
public static void main(String[] args) throws IOException {
final var filename = args[0];
final Path path = Paths.get(filename);
final List<String> lines = Files.readAllLines(path);
final Map<String, List<String>> buckets = new HashMap<>();
lines.forEach(
line -> {
final var parts = Arrays.asList(line.split(" "));
final var value = buckets.getOrDefault(parts.get(0), new ArrayList<>());
value.add(String.join(" ", parts.subList(1, parts.size())));
buckets.put(parts.get(0), value);
});
final List<Command> commands = new ArrayList<>();
buckets
.forEach((key, value) -> commands.add(new Command(key, value)));
commands.sort(Comparator.comparing(c -> c.args().size()));
Collections.reverse(commands);
final List<String> htmlLines = generateHtml(commands);
htmlLines.forEach(System.out::println);
}
private static List<String> generateHtml(final List<Command> commands) {
final List<String> htmlLines = new ArrayList<>();
htmlLines.add("""
<!DOCTYPE html>
<html>
<head>
<style>
details {
border: 1px solid #aaa;
border-radius: 4px;
padding: .5em;
margin-block: .5em;
background-color: lavender;
overflow-x: auto;
}
summary {
font-weight: bold;
margin: -0.5em -0.5em 0;
padding: 0.5em;
background-color: white;
}
details[open] {
padding: 0.5em;
}
details[open] summary {
border-bottom: 1px solid #aaa;
margin-bottom: 0.5em;
}
pre {
height: 2em;
}
body {
max-width: 130ch;
margin-inline: auto;
}
</style>
</head>
<body>
""");
commands.forEach(
c -> {
htmlLines.add("<details>");
htmlLines.add("<summary>" + c.args().size() + " - " + c.command() + "</summary>");
htmlLines.add("<ul>");
Collections.sort(c.args());
c.args()
.forEach(
s -> {
htmlLines.add("<li><pre>");
htmlLines.add(s.isEmpty() ? "<em>empty</em>" : s);
htmlLines.add("</pre></li>");
});
htmlLines.add("</ul>");
htmlLines.add("</details>");
});
htmlLines.add(" </body>");
htmlLines.add("</html>");
return htmlLines;
}
}