Run man bash
and search for section Special Parameters. For _
you'll find:
expands to the last argument to the
previous command, after expansion.
A slightly more verbose version might be...
expands to the last argument to the previous command as it was received by that command, i.e. after going through Shell Expansion
Instead of explaining that further I think a couple examples will really help.
Example 1
$ echo "$(date +%s)" # %s is seconds since epoch
1536984661
$ echo $_ !$ "$(date +%s)" # don't hit enter until a few seconds pass
1536984661 1536984665 1536984665
$_
gets the actual value passed to the preceding echo
so it resolves to a seconds-since-epoch value in the past. Meanwhile, since !$
is simply replaced with the typed value of the last argument, i.e. "$(date +%s)"
, it resolves to the same later time as the argument that follows it.
Example 2
$ foo='a b c'
$ echo "$foo" # expands to single arg: 'a b c'
a b c
$ echo "$_"
a b c
$ #---------
$ echo $foo # expands to three args: a b c
a b c
$ echo "$_"
c
Notice that the second time $foo
was echoed it wasn't quoted. So before being passed to echo
it went through variable/parameter expansion and word splitting. That expansion/splitting resulted in a, b and c being passed as three separate parameters to echo
and since $_
is assigned just the last one we see only c. (If !$
were used a b c would still be displayed since, again, it's just replaced with the value you typed in, i.e. $foo
.)
In addition to the above there's a bit more practical value to $_
versus !$
because the former 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 any of that with !$
.
Finally, 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.