6

I've always used !$ to refer to the last argument of the previous command.

e.g.

~/dir » mkdir birthday
~/dir » cd !$
~/dir » cd birthday

However I've started seeing tutorials use $_ in place of !$

I've tested the two and the only difference I've noticed is subtle, but it appears that $_ will evaluate and execute the command whereas !$ will let you sanity check the output before hitting Enter, but is this the only difference between $_ and !$?

Update: flagged as a duplicate? This question may be answered by the accepted answer for the suggested question, but only because the answer is more detailed than the question required.

Searching for What does _$ mean? does yield the suggested duplicate, however I already and I knew prior to asking what !$ did, and there is nothing that suggests that question might also answer other questions.

Luke
  • 203

1 Answers1

10

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.

B Layer
  • 5,171