70

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

74

!$ is a word designator of history expansion; it expands to the last word of the previous command in history. In other words, the last word of the previous entry in history. This word is usually the last argument to the 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 the last argument of the 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 no longer one of shell standard special parameters. 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
4
  • 17
    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
  • This is really about the documentation overloading the word "argument" with two different meanings. $_ gets the last command line argument that actually went to the executed command though the execve() call. That happens after the command line is fully parsed, after all expansions etc. But !$ looks at words at the command line really early, before much parsing has happened. It does some things like detecting quotes there, but not much else.
    – ilkkachu
    May 27, 2022 at 13:13
  • You can see a difference too with e.g. echo $RANDOM, where $_ would contain the actual value passed, and hence echo $_ would print the same random number again. But !$ would turn into $RANDOM, which would then be expanded as usual, giving a different number. Or with false && echo bar where $_ would give false (the last argument to the last executed command), but !$ would give bar (the last word on the last command line).
    – ilkkachu
    May 27, 2022 at 13:14
2

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.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .