The Stack Overflow podcast is back! Listen to an interview with our new CEO.
10 A more accurate description of how my original answer was inaccurate. :)
source | link

UPDATE: While the rest of it ismostly accurate, my answer got ais lacking some key part wrong; specificallyinformation and perhaps even misleading about how/when $_ actually gets a value. That first quote is meant to read like this:

UPDATE: While the rest of it is accurate, my answer got a key part wrong; specifically about how/when $_ actually gets a value. That first quote is meant to read like this:

UPDATE: While mostly accurate, my answer is lacking some key information and perhaps even misleading about how/when $_ actually gets a value. That first quote is meant to read like this:

9 Correct explanation about expansion of $_
source | link

UPDATE: While the rest of it is accurate, my answer got a key part wrong; specifically about how/when $_ actually gets a value. That first quote is meant to read like this:

expands to the last argument to the previous command as it was received by that command, i.e. after going through Shell Expansion

Best way to illustrate is with a couple examples:

$ echo "$(date +%s)" # %s is seconds since epoch
1536984661
$ echo $_  "$(date +%s)" # don't hit enter until a few seconds pass
1536984661 1536984665

So $_ gets the actual seconds-since-epoch calculated at the time the first echo executed not something closer to the 1536984665 of the second date invocation.

Another one:

$ foo='a b c'
$ echo "$foo"
a b c
$ echo "$_"
a b c
$ #---------
$ echo $foo
a b c
$ echo "$_"
c

In the second case we didn't quote $foo. Before being passed to echo it went through variable/parameter expansion followed by word splitting. The lack of quotes resulted in "a", "b" and "c" being passed as three separate parameters and $_ got the last, i.e. "c".


UPDATE: While the rest of it is accurate, my answer got a key part wrong; specifically about how/when $_ actually gets a value. That first quote is meant to read like this:

expands to the last argument to the previous command as it was received by that command, i.e. after going through Shell Expansion

Best way to illustrate is with a couple examples:

$ echo "$(date +%s)" # %s is seconds since epoch
1536984661
$ echo $_  "$(date +%s)" # don't hit enter until a few seconds pass
1536984661 1536984665

So $_ gets the actual seconds-since-epoch calculated at the time the first echo executed not something closer to the 1536984665 of the second date invocation.

Another one:

$ foo='a b c'
$ echo "$foo"
a b c
$ echo "$_"
a b c
$ #---------
$ echo $foo
a b c
$ echo "$_"
c

In the second case we didn't quote $foo. Before being passed to echo it went through variable/parameter expansion followed by word splitting. The lack of quotes resulted in "a", "b" and "c" being passed as three separate parameters and $_ got the last, i.e. "c".

8 Figured out how $_ could be more useful than !$
source | link

man bash, search for section Special Parameters. For _ you'll find:

expands to the last argument to the previous command, after expansion.

So in a command line context the difference is the expansion. For example if I have a variable FOO set to 'bar' and I execute echo $FOO then $_ on the next line will result in bar being submitted in the next command while !$ will result in $FOO.

This behavior might not be obvious by default but with shopt -s histverify the distinction isit's a little more evident. (With histverify enabled Bash will not submit history expansion immediately after Enter, it will first display the command, post-expansion, and wait for a second Enter). So we would see something like this after FOO=bar:

$ echo $FOO
bar
$ ls !$ $_
# We hit Enter and then see
$ ls $FOO $_ 

$_ is not expanded until the command is submitted during parameter expansion ('parameter expanstion'along with $FOO). It's a subtle distinction but there you go. (Would love to hear of a truly practical use for one vs the other in

Probably the comments..more interesting/useful distinction is that $_ is a variable.beyond So, for example, if the side effects in historylast parameter were /some/file/path.txt you could get it's length ${#_} -> 19, extract filename only ${_##*/} -> path.txt, change the root ${_//some/other} -> /other/file/path.txt and suchso on.) Can't do all of that with !$.


Note that $_ has different meanings in other contexts:

At shell startup, set to the absolute pathname used to invoke the shell or shell script being executed as passed in the environment or argument list.

...and...

Also set to the full pathname used to invoke each command executed and placed in the environment exported to that command.

...and...

When checking mail, this parameter holds the name of the mail file currently being checked.

man bash, search for section Special Parameters. For _ you'll find:

expands to the last argument to the previous command, after expansion.

So in a command line context the difference is the expansion. For example if I have a variable FOO set to 'bar' and I execute echo $FOO then $_ on the next line will result in bar being submitted in the next command while !$ will result in $FOO.

This behavior might not be obvious by default but with shopt -s histverify the distinction is a little more evident. (With histverify enabled Bash will not submit history expansion immediately after Enter, it will first display the command, post-expansion, and wait for a second Enter). So we would see something like this after FOO=bar:

$ echo $FOO
bar
$ ls !$ $_
# We hit Enter and then see
$ ls $FOO $_ 

$_ is not expanded until the command is submitted ('parameter expanstion'). It's a subtle distinction but there you go. (Would love to hear of a truly practical use for one vs the other in the comments...beyond the side effects in history and such.)


Note that $_ has different meanings in other contexts:

At shell startup, set to the absolute pathname used to invoke the shell or shell script being executed as passed in the environment or argument list.

...and...

Also set to the full pathname used to invoke each command executed and placed in the environment exported to that command.

...and...

When checking mail, this parameter holds the name of the mail file currently being checked.

man bash, search for section Special Parameters. For _ you'll find:

expands to the last argument to the previous command, after expansion.

So in a command line context the difference is the expansion. For example if I have a variable FOO set to 'bar' and I execute echo $FOO then $_ on the next line will result in bar being submitted in the next command while !$ will result in $FOO.

This behavior might not be obvious by default but with shopt -s histverify it's a little more evident. (With histverify enabled Bash will not submit history expansion immediately after Enter, it will first display the command, post-expansion, and wait for a second Enter). So we would see something like this after FOO=bar:

$ echo $FOO
bar
$ ls !$ $_
# We hit Enter and then see
$ ls $FOO $_ 

$_ is not expanded until the command is submitted during parameter expansion (along with $FOO). It's a subtle distinction but there you go.

Probably the more interesting/useful distinction is that $_ is a variable. So, for example, if the last parameter were /some/file/path.txt you could get it's length ${#_} -> 19, extract filename only ${_##*/} -> path.txt, change the root ${_//some/other} -> /other/file/path.txt and so on. Can't do all of that with !$.


Note that $_ has different meanings in other contexts:

At shell startup, set to the absolute pathname used to invoke the shell or shell script being executed as passed in the environment or argument list.

...and...

Also set to the full pathname used to invoke each command executed and placed in the environment exported to that command.

...and...

When checking mail, this parameter holds the name of the mail file currently being checked.

7 More refinement of the histverify section (sigh)
source | link
6 More refinement of the histverify section (sigh)
source | link
5 Better explanation of 'histverify'
source | link
4 added 502 characters in body
source | link
3 added 78 characters in body
source | link
2 added 78 characters in body
source | link
1
source | link