man bash
, procure a seção Parâmetros especiais . Por _
você encontrará:
expands to the last argument to the previous command, after expansion.
Assim, em um contexto de linha de comando, a diferença é a expansão. Por exemplo, se eu tiver uma variável FOO
definida como 'bar' e executar echo $FOO
, então $_
na próxima linha resultará em bar
sendo enviado no próximo comando, enquanto !$
resultará em $FOO
.
Esse comportamento pode não ser óbvio por padrão, mas com shopt -s histverify
é um pouco mais evidente. (Com histverify
habilitado, o Bash não enviará a expansão do histórico imediatamente após Enter , primeiro exibirá o comando, pós-expansão, e aguardará um segundo Enter ). Então, veríamos algo assim depois de FOO=bar
:
$ echo $FOO
bar
$ ls !$ $_
# We hit Enter and then see
$ ls $FOO $_
$_
não é expandido até que o comando seja submetido durante a expansão do parâmetro (juntamente com $FOO
). É uma distinção sutil, mas lá vai você.
Provavelmente, a distinção mais interessante / útil é que $_
é uma variável . Portanto, por exemplo, se o último parâmetro for /some/file/path.txt
, você poderá obter o comprimento ${#_} -> 19
, extrair apenas o nome do arquivo ${_##*/} -> path.txt
, alterar a raiz ${_//some/other} -> /other/file/path.txt
e assim por diante. Não é possível fazer tudo isso com !$
.
Observe que $_
tem significados diferentes em outros contextos:
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.
... e ...
Also set to the full pathname used to invoke each command executed and placed in the environment exported to that command.
... e ...
When checking mail, this parameter holds the name of the mail file currently being checked.
ATUALIZAÇÃO: Embora seja mais preciso, minha resposta carece de algumas informações importantes e talvez até enganosas sobre como / quando $_
realmente obtém um valor. Essa primeira citação destina-se a ler assim:
expands to the last argument to the previous command as it was received by that command, i.e. after going through Shell Expansion
A melhor maneira de ilustrar é com alguns exemplos:
$ echo "$(date +%s)" # %s is seconds since epoch
1536984661
$ echo $_ "$(date +%s)" # don't hit enter until a few seconds pass
1536984661 1536984665
Portanto, $_
obtém os segundos reais desde a época calculados no momento em que o primeiro echo
foi executado não perto da invocação 1536984665
do segundo date
.
Outro:
$ foo='a b c'
$ echo "$foo"
a b c
$ echo "$_"
a b c
$ #---------
$ echo $foo
a b c
$ echo "$_"
c
No segundo caso, não citamos $foo
. Antes de ser passado para echo
, passou pela expansão de variável / parâmetro seguida de divisão de palavras. A falta de aspas resultou em "a", "b" e "c" sendo passados como três parâmetros separados e $_
obteve o último, ou seja, "c".