Há uma strong diferença entre uma palavra-chave embutida e uma na palavra-chave, na maneira como o Bash analisa seu código. Antes de falarmos sobre a diferença, vamos listar todas as palavras-chave e recursos:
Construções:
$ compgen -b
. : [ alias bg bind break
builtin caller cd command compgen complete compopt
continue declare dirs disown echo enable eval
exec exit export false fc fg getopts
hash help history jobs kill let local
logout mapfile popd printf pushd pwd read
readarray readonly return set shift shopt source
suspend test times trap true type typeset
ulimit umask unalias unset wait
Palavras-chave:
$ compgen -k
if then else elif fi case
esac for select while until do
done in function time { }
! [[ ]] coproc
Observe que, por exemplo, [
é interno e que [[
é uma palavra-chave. Vou usar esses dois para ilustrar a diferença abaixo, já que são operadores bem conhecidos: todos os conhecem e os usam regularmente (ou deveriam).
Uma palavra-chave é escaneada e entendida pelo Bash bem no início de sua análise. Isso permite, por exemplo, o seguinte:
string_with_spaces='some spaces here'
if [[ -n $string_with_spaces ]]; then
echo "The string is non-empty"
fi
Isso funciona bem, e o Bash terá um bom resultado
The string is non-empty
Note que não citei $string_with_spaces
. Considerando o seguinte:
string_with_spaces='some spaces here'
if [ -n $string_with_spaces ]; then
echo "The string is non-empty"
fi
mostra que Bash não está feliz:
bash: [: too many arguments
Por que funciona com palavras-chave e não com recursos internos? porque quando o Bash analisa o código, ele vê [[
, que é uma palavra-chave, e entende muito cedo que é especial. Então, ele procurará o fechamento ]]
e tratará o interior de maneira especial. Um builtin (ou comando) é tratado como um comando real que será chamado com argumentos. Neste último exemplo, o bash entende que deve executar o comando [
com argumentos (mostrado um por linha):
-n
some
spaces
here
]
desde a expansão de variáveis, remoção de cotações, expansão de nome de caminho e divisão de palavras. O comando [
acaba sendo construído no shell, então ele executa com estes argumentos, o que resulta em um erro, daí a reclamação.
Na prática, você vê que essa distinção permite um comportamento sofisticado, que não seria possível com builtins (ou comandos).
Ainda na prática, como você pode distinguir um elemento interno de uma palavra-chave? esta é uma experiência divertida para realizar:
$ a='['
$ $a -d . ]
$ echo $?
0
Quando o Bash analisa a linha $a -d . ]
, não vê nada de especial (ou seja, sem aliases, sem redirecionamentos, sem palavras-chave), por isso apenas executa a expansão de variáveis. Após expansões de variáveis, ele vê:
[ -d . ]
, portanto, executa o comando (incorporado) [
com os argumentos -d
, .
e ]
, o que, obviamente, é verdadeiro (isso só testa se .
é um diretório).
Agora olhe:
$ a='[['
$ $a -d . ]]
bash: [[: command not found
Oh. Isso porque, quando o Bash vê essa linha, ela não vê nada especial e, portanto, expande todas as variáveis e, eventualmente, vê:
[[ -d . ]]
Neste momento, as expansões de alias e a varredura de palavra-chave são feitas há muito tempo e não serão mais executadas. Por isso, Bash tenta encontrar o comando chamado [[
, não o encontra e reclama.
Na mesma linha:
$ '[' -d . ]
$ echo $?
0
$ '[[' -d . ]]
bash: [[: command not found
e
$ \[ -d . ]
$ echo $?
0
$ \[[ -d . ]]
bash: [[: command not found
A expansão de alias é algo bastante especial também. Todos vocês fizeram o seguinte pelo menos uma vez:
$ alias ll='ls -l'
$ ll
.... <list of files in long format> ....
$ \ll
bash: ll: command not found
$ 'll'
bash: ll: command not found
O raciocínio é o mesmo: a expansão de alias ocorre muito antes da expansão de variáveis e da remoção de cotações.
Palavra-chave v.s. Alias
Agora, o que você acha que acontece se definirmos um alias como uma palavra-chave?
$ alias mytest='[['
$ mytest -d . ]]
$ echo $?
0
Oh, funciona! Por isso, os aliases podem ser usados para alias de palavras-chave! bom saber.
Conclusão: os builtins realmente se comportam como comandos: eles correspondem a uma ação que está sendo executada com argumentos que sofrem expansão direta de variáveis e divisão de palavras e globbing. É realmente como ter um comando externo em algum lugar em /bin
ou /usr/bin
que é chamado com os argumentos dados após a expansão da variável, etc. Note que quando eu digo é realmente como ter um comando externo Eu só quero dizer com relação a argumentos, divisão de palavras, globbing, expansão de variáveis, etc. Um builtin pode modificar o estado interno do shell!
As palavras-chave, por outro lado, são verificadas e compreendidas muito cedo e permitem um comportamento sofisticado do shell: o shell poderá proibir a divisão de palavras ou a expansão do nome do caminho, etc.
Agora, veja a lista de referências e palavras-chave e tente descobrir por que alguns precisam ser palavras-chave.
!
é uma palavra-chave. Parece que seria possível imitar seu comportamento com uma função:
not() {
if "$@"; then
return false
else
return true
fi
}
mas isso proibiria construções como:
$ ! ! true
$ echo $?
0
ou
$ ! { true; }
echo $?
1
Mesmo para time
: é mais poderoso ter uma palavra-chave para poder sincronizar comandos e pipelines compostos complexos com redirecionamentos:
$ time grep '^#' ~/.bashrc | { i=0; while read -r; do printf '%4d %s\n' "$((++i))" "$REPLY"; done; } > bashrc_numbered 2>/dev/null
Se time
fosse um mero comando (mesmo embutido), veria apenas os argumentos grep
, ^#
e /home/gniourf/.bashrc
, tempo isso, e então sua saída passaria pelas partes restantes do pipeline . Mas com uma palavra-chave, o Bash pode lidar com tudo! pode time
o pipeline completo, incluindo os redirecionamentos! Se time
fosse um mero comando, não poderíamos fazer:
$ time { printf 'hello '; echo world; }
Experimente:
$ \time { printf 'hello '; echo world; }
bash: syntax error near unexpected token '}'
Tente consertar (?):
$ \time { printf 'hello '; echo world;
time: cannot run {: No such file or directory
Desesperado.
Palavra-chave vs alias?
$ alias mytime=time
$ alias myls=ls
$ mytime myls
O que você acha que acontece?
Realmente, um builtin é como um comando, exceto que ele é construído no shell, enquanto que uma palavra-chave é algo que permite um comportamento sofisticado! podemos dizer que é parte da gramática da concha.