Differences between BASH last argument reference methods

Note 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 its own distinct use case. The first distinction is that the first two can't be used in shell scripts and are actually command history expansions. With the _ variable (dereferenced by $_) you must consider the execution order.

!$

!$ has two parts to it. The ! means reference the previous line and $ means get the last argument. You can also do stuff like this !-2$ which means get the 2nd to last command 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 don't know what is being referenced. However this syntax does allow you to use modifiers against the argument to change it. See the HISTORY EXPANSION section of the bash man page.

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.

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.

Run this:

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. 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.

$_

$_ 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.

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

Hope that clears it up.

--Deltaray

Created: 2014-05-20

Go to climagic homepage

blog comments powered by Disqus