17th September 2019 - Guide , Presentations , Linux
History, as you might know, is of course the GNU History Library. It is a utility that allows us to read, repeat, and track the commands we run in our shells. Apparently a lot of people feel really strongly about history, so I’ve added some quotes in this article to underline how nice of a feature this is.
A library is the delivery room for the birth of ideas, a place where history comes to life.
- Norman Cousins
This was originally held as a presentation for "Tromsøstudentenes dataforening", the Tromsø student union for computer scientist students.
The history command
But let’s take a look at history, after all we’re always told to learn from it. Most of you are probably familiar with hitting the up key on your keyboard in order to bring back the last command you typed. Or continue to press that button until you find whichever command you could’ve sworn was just a second ago while your finger is slowly going numb. But what if I told you that there was more to history than this?
What is history? An echo of the past in the future; a reflex from the future on the past.
- Victor Hugo
So let’s take a look at what the manual has to say about history:
DESCRIPTION Many programs read input from the user a line at a time. The GNU History library is able to keep track of those lines, associate arbitrary data with each line, and utilize information from previous lines in composing new ones.
What this means is that in our shell this library is somehow used to store the history of what we’re typing, and should offer us some ways to interact with this history. Now the specifics of this differs from shell to shell, I’m going to use ZSH here today but all of this should work fine in bash and most other shells as well. So what can we actually do with history. Well, let’s try:
$ history 42 cd /home/peter/Documents/Faxes/Sent\ Faxes 43 feh maxfax.png 44 cd ~/Work 45 vim important_documentation.md 46 pandoc -o doc.html important_documentation.md 47 fg 48 pandoc -o doc.html important_documentation.md 49 fg 50 cd ~/Documents/TD\ Talks/ 51 vim history.md 52 pandoc -s -c pandoc.css history.md -o history.html 53 fg 54 pandoc -s -c pandoc.css history.md -o history.html 55 fg
The basic history command simply prints out the last couple of commands you’ve run, along with a number. This number is the identifier of that event, and we can use this to refer to the event. For that is the power of history, we can recall the good parts we want to relive. But this list is a bit short, if we are looking for things further back than a couple of commands it would be useful to have a longer list. This can be achieved with the
fc program, short for “fix command”. It is essentially a utility that allows you to list your history, or edit commands in an editor (such as Vim) before running it again. To use it to list all of history you can simply use
fc -l 0 which tells it to list every entry starting with id 0. You can also pass ranges and other options. On my machines I have created an alias for
history that instead of running the shell built-in runs
fc -l 0 so I always get the long list. Note that this is just a program like any other that returns text, so you can pass this through something like
grep to find a command you’re looking for. But as we will see soon there are easier ways to recall commands, so back to history!
The more you know of your history, the more liberated you are.
- Maya Angelou
Picking parts from history
Let’s dive in to how we can pick parts out of history and use them for our benefit. Reading on in the manual of history leads us to the section on “History expansion”:
HISTORY EXPANSION The history library supports a history expansion feature that is identical to the history expansion in bash. This section describes what syntax features are available. History expansions introduce words from the history list into the input stream, making it easy to repeat commands, insert the arguments to a previous command into the current input line, or fix errors in previous commands quickly. [...]
So how can we stroll back through history? Well it’s fairly simple! Assume our history is the one from the output above, and let’s say we wanted to run the
vim history.md command. Maybe the most rudimentary way is to simply specify the event identifier appended with a
$ !51 vim history.md <Vim would start here>
As we can see the history expansion shows the command we expanded, and then immediately runs it. Now again assuming the same history we can also do a relative expansion by using a negative number:
$ !-5 vim history.md <Vim would start here>
This is useful if we want to run a command that we remember running a couple commands ago. There is also a useful shortcut for
!-1 which is simply
!!. Note that these are “expansions” not just simple “execute this command”, so if you for example forgot
sudo on your last command it can easily be re-run with sudo by doing
sudo !!. Of course you can also add arguments to the end of this. Another way to expand a previous command is to name the program we called. So again using the
vim history.md command as an example simply typing
!vim would re-run the last command starting with “vim” from our history. Note however that it is starting with, so to run the last “pandoc” command we could also run something like
!pan. Another useful thing is to search by any word in the command. Let’s say you’re working in Git and want to run the last “push” command you did, assuming you don’t have any other commands with the word “push” in it you could run
!?push and it would re-run it. Since this is a string based search it also allows us to use spaces and other things that the
!git search would not permit, so if you had a command with the word “push” in it that was not your git command you could do
!?git push to get the actual last git push command.
History, despite its wrenching pain, cannot be unlived, but if faced with courage, need not be lived again.
- Maya Angelou
All this is well and good, but what if we don’t just want to run the exact same command but rather want to change the future based on our history? After all we’re supposed to not repeat the mistakes we find through history right? Well for this purpose we have some options, the simplest of which are word designators. Essentially each command you run is split into “words”, I put words in quotes here because it’s more like the arguments you pass in. So for example the command
echo "Hello world" is not three words, but the two words “echo” and “Hello world”. These words are 0 indexed, so if we want to get the program we last executed, but without any argumenst, we can use:
As we learned earlier
!! is the last event, and
:0 is how we select a word, in this case the zeroeth. Ranges are also allowed so something like this will take both arguments and pass them to a new command:
$ cp somefile.txt anothername.txt $ mv !!:1-2 mv somefile.txt anothername.txt
There are also a couple of convenient aliases if you don’t feel like counting such as
^ for the first argument (word 1), and
$ for the last argument (or the program name if there were no arguments). So if you want to run the last command without the first argument you can do something like:
This will expand to the name of the last command(
!!:0) and the list of arguments from number two all the way to the last one (
!!:2-$). But this can be further shortened by
* is shorthand for
-$. Using just
* chooses all the arguments, but not the command name. There are more of these, and they can all be found in the manual. Of course this can be used together with the event selection things, one example to run the
mingw cross compiler with all the arguments passed to the last call to the regular
!gcc:* here expands to all the arguments passed to the last event that starts with
History isn’t just the story of bad people doing bad things. It’s quite as much a story of people trying to do good things. But somehow, something goes wrong.
C. S. Lewis
But what if you’ve written something wrong or want to use a different path for a command you recently called? You could of course use the previously mentioned
fc program that allows us to fix a command in an editor before running it. But as a quicker method for common tasks we have modifiers, these allow us to do some simple path handling, and run search-and-replace queries on the string. Modifiers can also be used to print a command, which might be especially helpful if we want to make sure that we don’t run the wrong command. These modifiers come after the word selectors, if you have any, or they take their place. So for example if we wanted to print the last command, or print the command two events ago without the program name we can do:
$ echo Hello world my old friend Hello world my old friend $ !!:p echo Hello world my old friend $ !-2:1*:p Hello world my old friend
Note that if you drop the
p from these examples the first one will print the matching event, and then the echo statement will write out the same string. And the second example will just throw an error saying that
Hello is not a valid command.
Of course just printing our commands isn’t really all that interesting, so let’s look at a different modifier, the search-and-replace. It looks a bit like a regex but in fact it’s a custom format. So let’s say we wanted to change the user in a
$ rm /home/tim/somefile.txt rm: cannot remove '/home/tim/somefile.txt': No such file or directory $ !!:s/tim/peter/ rm /home/peter/somefile.txt
Similar to how
sed works we can use any delimiter, not only
/, so instead of having to escape all
/s if you want to replace a path you can use e.g.
| instead. If you don’t want to replace something, but say append something to it, or add quotes around it you can use
& to get the match from the left hand side:
$ rm /home/tim/somefile.txt rm: cannot remove '/home/tim/somefile.txt': No such file or directory $ !!:s|tim|&/peter| rm /home/tim/peter/somefile.txt
We can also use
& alone to perform the last substitution that was made, which can save some typing:
$ mv somefile.txt myfile.txt $ cp !!:2:s|file|text| !!:1:& mv mytext.txt sometext.txt
Here the first expansion takes the second argument and replaces “file” with “text”, and then the second expansion takes the first argument and performs the same substitution. Again there is more to this, so have a look in the manual. In the manual you will also find more modifiers that can do some common path modifications like dropping the file extension, removing the path but keeping the file name, etc.
Now history is all well and good, but is there something we can do to improve this workflow? I mentioned in the beginning that I used an alias for the
history command to get a complete list. Along with this you can also increase the amount of entries that are actually stored by changing the
SAVEHIST which changes how many entries are read from
HISTFILE when the terminal emulator is opened, and how many are saved when it’s closed respectively. Another tweak that might be practical is to add
%h to your PROMPT variable. The PROMPT variable as you probably know is used to define what information is printed in your terminal when it’s awaiting input. Usually this contains the username, the current working directory, and maybe some other information. By adding
%h to this you will get the current event number, the same numbers that show up when calling
history. So if you only want to repeat a command that is still visible in your terminal, you can just read out the number instead of having to go via
history or count backwards to it.
I hope you’ve enjoyed this brief look at history. It’s an immensely powerful tool that can save you a lot of typing and definitely something that should be a part of your terminal-fu toolkit. To finish off I will leave you with a final quote from Theodore Roosevelt that highlights why you should take the time to learn how to use history:
Never throughout history has a man who lived a life of ease left a name worth remembering.
- Theodore Roosevelt