$_ vs !$ – Last Argument of Preceding Command and Output Redirection

bashhistory-expansionshell

The question is about special variables. Documentation says:

!!:$

designates the last argument of the preceding command. This may
be shortened to !$.


($_, an underscore.) 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. Subsequently, expands to the last argument to the previous command after expansion. Also set to the full pathname used to invoke each command executed and placed in the environment
exported to that command.

There must be some difference I cannot catch, because:

$ echo "hello" > /tmp/a.txt
$ echo "!$"
echo "/tmp/a.txt"
/tmp/a.txt

$ echo "hello" > /tmp/a.txt
$ echo $_
hello

What is the difference?

Best Answer

!$ is a word designator of history expansion, it expands to the last word of previous command in history. IOW, the last word of previous entry in history. This word is usually the last argument to command, but not in case of redirection. In:

echo "hello" > /tmp/a.txt

the whole command 'echo "hello" > /tmp/a.txt' appeared in history, and /tmp/a.txt is the last word of that command.

_ is a shell parameter, it expands to last argument of previous command. Here, the redirection is not a part of arguments passed to the command, so only hello is the argument passed to echo. That's why $_ expanded to hello.

_ is not one of shell standard special parameters anymore. It works in bash, zsh, mksh and dash only when interactive, ksh93 only when two commands are on separated lines:

$ echo 1 && echo $_
1
/usr/bin/ksh

$ echo 1
1
$ echo $_
1