Há uma forte diferença entre um builtin e uma 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 componentes internos:
Builtins:
$ 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, [
é um builtin e essa [[
é uma palavra-chave. Usarei esses dois para ilustrar a diferença abaixo, pois são operadores bem conhecidos: todos os conhecem e os usam regularmente (ou deveriam).
Uma palavra-chave é verificada e entendida pelo Bash muito cedo em 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á uma saída feliz
The string is non-empty
Note que eu 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 builtins? porque quando o Bash analisa o código, ele vê [[
qual é uma palavra-chave e entende muito cedo que é especial. Por isso, procurará o fechamento ]]
e tratará o interior de uma 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 que ocorre expansão variável, remoção de cotação, expansão de nome de caminho e divisão de palavras. O comando[
acaba sendo construído no shell, portanto, ele é executado com esses 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 os builtins (ou comandos).
Ainda na prática, como você pode distinguir um builtin de uma palavra-chave? este é um experimento divertido de executar:
$ a='['
$ $a -d . ]
$ echo $?
0
Quando o Bash analisa a linha $a -d . ]
, ele não vê nada de especial (ou seja, sem alias, sem redirecionamentos, sem palavras-chave), portanto, apenas realiza a expansão variável. Após expansões variáveis, ele vê:
[ -d . ]
assim executa o comando (embutido) [
com argumentos -d
, .
e ]
, que, claro é verdadeiro (isto só testa se .
é um diretório).
Agora veja:
$ a='[['
$ $a -d . ]]
bash: [[: command not found
Oh. Isso ocorre porque, quando o Bash vê essa linha, ele não vê nada de especial e, portanto, expande todas as variáveis e, eventualmente, vê:
[[ -d . ]]
No momento, há muito tempo que expansões de alias e verificação de palavras-chave são executadas e não serão mais executadas, então o 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 também é algo especial. 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 do alias ocorre muito antes da expansão variável e da remoção da cotação.
Alias vs palavra-chave
Agora, o que você acha que acontece se definirmos um alias para ser uma palavra-chave?
$ alias mytest='[['
$ mytest -d . ]]
$ echo $?
0
Oh, isso funciona! para que aliases possam ser usados para alias palavras-chave! bom saber.
Conclusão: builtins realmente se comportam como comandos: eles correspondem a uma ação sendo executada com argumentos que sofrem expansão direta de variável e divisão e globbing de palavras. É realmente como ter um comando externo em algum lugar /bin
ou /usr/bin
é chamado com os argumentos dados após a expansão de variáveis, etc. Observe que quando digo é realmente como ter um comando externo quero dizer apenas com relação a argumentos, divisão de palavras, globbing, expansão variável, 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, observe a lista de itens incorporados 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
O mesmo para time
: é mais poderoso ter uma palavra-chave para que possa programar 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
onde um mero comando (mesmo embutido), seria só vê os argumentos grep
, ^#
e /home/gniourf/.bashrc
, tempo este, em seguida, sua saída seria percorrer as restantes partes 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; }
Tente:
$ \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
Sem esperança.
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 uma palavra - chave é algo que permite um comportamento sofisticado! podemos dizer que faz parte da gramática do shell.