63

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?

2
  • 6
    !$ is history expansion, not a special variable, so I'd assume that the bash code handles them differently.
    – Jeff Schaller
    Mar 23, 2016 at 11:03
  • 5
    I don't have a definitive answer to this question, but: ① The Bourne shell language is one of, perhaps the, least internally consistent programming language still widely used today. Attempting to deduce general principles will get you nowhere. You just have to memorize all the special rules. Sorry. ② Note the words "after expansion", immediately after the phrase you highlighted, in the definition of $_. That's crucial. It might not explain the difference that puzzles you but it does explain a bunch of other ways $_ and !$ are not the same.
    – zwol
    Mar 23, 2016 at 13:00

2 Answers 2

67

!$ 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
2
  • 15
    One effect of !$ expansion vs $_ parameter is that echo $_ always shows in history as echo $_, while echo !$ shows the value that it was expanded to. E.g. echo one two then echo !$, then pressing up shows echo two.
    – Jacktose
    Oct 9, 2019 at 17:24
  • Not sure I understand what "only when interactive" applies to. I was just able to do something along the lines of mkdir -p foo && for f in ${array_of_filenames[@]}; do mv $f $_; done in a bash script
    – Fons MA
    Aug 18, 2020 at 4:57
1

I found it difficult if not impossible to use parameter substitution directly with !$.

For example if I could wget http://foo.com/my.tar then tar xf ${_##*/} but with !$ it is not possible without defining an intermediary variable.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.