Differences between BASH last argument reference methods

Note that this is in reference to BASH, other shells might not work the same way.

There are at least three ways to grab the "last argument" in the shell: !$, Meta+. and $_. Of course this all depends on what you mean by "last argument".

However, not all of these are the same thing and each has their own distinct use case. A major distinction between them is that the first two can't be used in shell scripts because they are command history expansions. Also, with the _ variable (dereferenced by $_) you must consider the execution order.

!$

!$ really has two parts to it. The ! means start a history substitution and by default this references the most recent line in your command history. The $ means get the last argument from that referenced line. Here is an example of how you would use it:

tar tvf hopefully-not-a-tarbomb.tar.gz
tar xvf !$
rm !$

In the second command after pressing enter, history expansion will change the !$ to be the last arg from the last command in your shell history, which in this case was hopefully-not-a-tarbomb.tar.gz. Then the third command in turn will remove the file hopefully-not-a-tarbomb.tar.gz.

You can also do stuff like this !-2$ which means get the 2nd to last line from the command history and take the last argument. Note that this expansion happens when you run the command, you don't see what is actually inserted until you press enter. This can be dangerous if you're unsure about what is being reference. A safer way is to preview what substitution will be made by appending :p to the end of the substitution like this:

rm !-2$:p

This will print the substituted command without running it and add it to your history so that if you're satisfied with the change you can just press uparrow and run it. If you want to use other modifiers with this syntax, you need to proceed each one with a ':'. See the HISTORY EXPANSION section of the bash man page for more details.

Meta+.

This is a readline history substitution that "live inserts" the last argument from the previous command from the command history. The location of the Alt key varies, but it is usually the Alt key on most systems. You can also press Esc and then . to get the same sequence, but the behavior is slightly different in that Alt+. acts as a single sequence when the second key is pressed and Esc will generate the escape character first before you press the second key to complete the sequence. To better understand this behavior, run 'cat -v' and try Alt+. vs. Esc+. to see the difference. For an example of how to use Meta+. run the following command to print out "a b c".

echo a b c

Now just press Meta+. and it should insert the 'c' from the previous command on your current command line.

The advantage here is that it "live inserts" the argument so that you can immediately see what is being inserted. I personally like using this over !$ because its safer. However unlike !$, you can't use any modifiers on the argument in the reference other than editing it after its inserted. Like the !$ it uses history substitution so this change will appear in your command history.

One other advantage to using Meta+. is that you can repeatedly press Meta+. and it continues to go back through your history to each previous "last argument" in turn. For this reason it is this author's goto operation of choice for getting the last argument in the majority of situations.

$_

$_ is a shell variable that is set at execution time. BASH keeps track of the last argument used in each parsed command and sets this variable to that value. This may not always be what you expect and its not always the last argument on the last line. Unlike the previous two methods, $_ can reference the current full command line you are entering on the command line. Here is an example command where you may want to use $_:

mkdir some-long-directory-name ; cd $_

The BASH parser first runs the mkdir command and sets the _ variable (dereferenced by $_) to be some-long-directory-name. Then when it parses the cd $_ command, it does a variable expansion and turns $_ into some-long-directory-name. See the Special Parameters section of the bash man page.

You can also use variable modifiers with the _ variable like you would any other variable. Consider a tar file that untars to a directory by the same name, but without the .tar.gz extension.

tar xvf program-1.2.3.tar.gz ; cd ${_%%.tar.gz}

!#:$

As a bonus I've included !#:$ here because it is a way of referencing the last argument of the current command you are actively typing up to the point of the reference. This is also a history substitution, except it acts on the current line. !# means reference the current line up to that point, : is a way of separating the line reference from the word reference in one of these history references and $ means the last word. Try the following to see how this works.

echo a b c !#:$

That should print out 'a b c c'. Now try this:

echo a b c !#:$ d e f

That should print out 'a b c c d e f'. So you see, it works on the line up to that point. Also note that unlike $_, this type of reference works on the entire line in the history, even across ; boundaries. Consider the output of this command:

echo a b c !#:$ d e f; !#:$

This will error out. To understand why you have to understand when history expansion actually happens. From the man page "History expansion is performed immediately after a complete line is read, before the shell breaks it into words." So the history substitution can reference anything in the line including the ; character separating commands itself. It can also include arithmetic expressions and variable names before they are expanded.

One symptom of using history expansion over $_ is that the expansion is saved in your history according to what it expanded into. This may be beneficial or not depending on what you want.

A good use for this type of reference is when you want to reference a filename, but change its extension when doing a conversion. For instance, converting a png to a jpg using the convert command:

convert -quality 75 Screenshot-2014-05-20.png !#:$:s/.png/.jpg/

That may seem like overkill for short filenames, but when dealing with long filenames or long paths it can be useful and once you get the syntax down it can be faster than typing the file/path all over again. It's not for the faint of heart though.

Summary table

Here is a summary table that you can reference to quickly see the differences between each method:


!$ Meta+. $_ !#:$
History Expansion Yes Yes No Yes
Variable Expansion No No Yes No
Can reference current line No No Yes Yes
References whole line (except comments) Yes Yes Not really Yes
Ease of use Medium Easy Medium Hard
Keypress repeatability No Yes No No
Can be used in scripts No No Yes No
Can apply history modifiers Yes No No Yes
Can apply variable modifiers No No Yes No
Potential for mistakes Moderate Low High High

Hope that clears it up.

--Deltaray

Created: 2014-05-20
Updated: 2018-09-18

Go to climagic homepage

blog comments powered by Disqus