$_ vs !$. Last argument of the preceding command and output redirection
Solution 1
!$
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
Solution 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.
Related videos on Youtube
Loom
Updated on September 18, 2022Comments
-
Loom almost 2 years
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?
-
Jeff Schaller over 8 years!$ is history expansion, not a special variable, so I'd assume that the bash code handles them differently.
-
zwol over 8 yearsI 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.
-
-
BlueJ over 4 yearsOne effect of
!$
expansion vs$_
parameter is thatecho $_
always shows in history asecho $_
, whileecho !$
shows the value that it was expanded to. E.g.echo one two
thenecho !$
, then pressing up showsecho two
. -
Fons MA almost 4 yearsNot 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 -
Admin about 2 yearsThis 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 theexecve()
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. -
Admin about 2 yearsYou can see a difference too with e.g.
echo $RANDOM
, where$_
would contain the actual value passed, and henceecho $_
would print the same random number again. But!$
would turn into$RANDOM
, which would then be expanded as usual, giving a different number. Or withfalse && echo bar
where$_
would givefalse
(the last argument to the last executed command), but!$
would givebar
(the last word on the last command line).