Qual é a diferença entre o shell builtin e a palavra-chave shell?

19

Quando executo esses dois comandos, obtenho

$ type cd
cd is a shell builtin
$ type if
if is a shell keyword

É claramente mostrado que cd é um shell embutido e if é uma palavra-chave do shell. Então, qual é a diferença entre o shell builtin e a palavra-chave?

    
por Avinash Raj 10.04.2014 / 04:47

2 respostas

20

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.

    
por gniourf_gniourf 26.02.2015 / 19:57
8

man bash chama-os SHELL BUILTIN COMMANDS . Então, um "shell builtin" é como um comando normal, como grep , etc., mas em vez de estar contido em um arquivo separado, é embutido no próprio bash . Isso faz com que eles tenham um desempenho mais eficiente do que os comandos externos.

Uma palavra-chave também é "codificada para o Bash, mas diferente de um código interno , uma palavra-chave não é em si um comando, mas uma subunidade de uma construção de comando. " Eu interpreto isso para dizer que as palavras-chave não têm função sozinha, mas exigem comandos para fazer qualquer coisa. (A partir do link, outros exemplos são for , while , do e ! , e há mais em minha resposta à sua outra pergunta.

    
por Sparhawk 10.04.2014 / 04:58