Por que `[` um shell está embutido e `[[` uma palavra-chave shell?

64

Tanto quanto eu sei, [[é uma versão aprimorada do [, mas estou confuso quando vejo [[como uma palavra-chave e [sendo mostrado como um interno.

[root@server ~]# type [
[ is a shell builtin
[root@server ~]# type [[
[[ is a shell keyword

TLDP diz

Um interno pode ser sinônimo de um comando do sistema com o mesmo nome, mas o Bash o reimplementa internamente. Por exemplo, o comando Bash echo não é o mesmo que / bin / echo, embora o comportamento deles seja quase idêntico.

e

Uma palavra-chave é uma palavra reservada, token ou operador. Palavras-chave têm um significado especial para o shell e, de fato, são os blocos de construção da sintaxe do shell. Como exemplos, por enquanto, faça e! são palavras-chave. Semelhante a um builtin, uma palavra-chave é codificada no Bash, mas ao contrário de um builtin, uma palavra-chave não é em si um comando, mas uma subunidade de uma construção de comando. [2]

Isso não deveria fazer tanto [e [[uma palavra-chave? Falta alguma coisa aqui? Além disso, esse link reafirma que ambos [e [[devem pertencer ao mesmo tipo.

Sree
fonte
2
Veja unix.stackexchange.com/a/56674
Stéphane Chazelas,
9
/ bin / [existe na minha máquina.
217 Joshua Joshua
2
Como uma simples demonstração de uma diferença entre os dois: if "[" $x -eq 3 ]funciona como esperado (porque Bash procura o comando chamado [, e este existe), mas if "[[" $x -eq 3 ]]não não funciona (porque mais uma vez Bash procura por um comando do nome apropriado, mas não há [[comando).
Kyle Strand
11
@ Josué O mesmo acontece /usr/bin/echo, mas isso não significa que não seja um builtin .
Jonathon Reinhart
Todas as bulitinas que também existem analisam externamente os argumentos se não foram construídas.
Joshua

Respostas:

80

A diferença entre [e [[é bastante fundamental.

  • [é um comando. Seus argumentos são processados ​​da mesma maneira que qualquer outro argumento de comando. Por exemplo, considere:

    [ -z $name ]

    O shell expandirá $namee executará a divisão de palavras e a geração de nome de arquivo no resultado, da mesma forma que faria com qualquer outro comando.

    Como exemplo, o seguinte falhará:

    $ name="here and there"
    $ [ -n $name ] && echo not empty
    bash: [: too many arguments
    

    Para que este funcione corretamente, são necessárias aspas:

    $ [ -n "$name" ] && echo not empty
    not empty
    
  • [[é uma palavra-chave shell e seus argumentos são processados ​​de acordo com regras especiais. Por exemplo, considere:

    [[ -z $name ]]

    O shell irá expandir $name, mas, ao contrário de qualquer outro comando, ele irá executar nem repartição de palavras nem geração de nome de arquivo no resultado. Por exemplo, o seguinte será bem-sucedido, apesar dos espaços incorporados name:

    $ name="here and there"
    $ [[ -n $name ]] && echo not empty
    not empty
    

Sumário

[ é um comando e está sujeito às mesmas regras que todos os outros comandos que o shell executa.

Como [[é uma palavra-chave, não um comando, no entanto, o shell a trata especialmente e opera sob regras muito diferentes.

John1024
fonte
+1. Obrigado. Você poderia fornecer a fonte (referência) sob quais regras um comando e uma palavra-chave operam?
Tim
@Tim As regras sob as quais os comandos operam estão detalhadas em man bash. Consulte, em particular, as seções intituladas "EXPANSÃO SIMPLES DE COMANDO" e "EXECUÇÃO DE COMANDO". Além [[, outros festança palavras-chave incluem if, then, while, e 'caso'. Não há regras gerais para palavras-chave: cada palavra-chave é um caso especial. man bash inclui detalhes para cada um.
John1024
62

No V7 Unix - onde o shell Bourne fez sua estréia - [foi chamado test, e existia apenas como /bin/test. Então, código que você escreveria hoje como:

if [ "$foo" = "bar" ] ; then ...

você teria escrito como

if test "$foo" = "bar" ; then ...

Esta segunda notação ainda existe, e eu acho que é mais claro sobre o que está acontecendo: você está chamando um comando chamado test, que avalia seus argumentos e retorna um código de status de saída que ifusa para decidir o que fazer a seguir. Esse comando pode ser incorporado ao shell ou pode ser um programa externo.¹

[como uma alternativa que testveio mais tarde.² Pode ser um sinônimo interno para test, mas também é fornecido como /bin/[em sistemas modernos para shells que não o possuem como interno.

[e testpode ser implementado usando o mesmo código. Este é o caso para /bin/[e /bin/testno OS X, onde esses são links físicos para o mesmo executável.³ Como resultado, a implementação ignora completamente a trilha ]: não é necessária se você chamar assim /bin/[e não se queixar se fazer fornecê-lo a /bin/test.⁴

Nada dessa história afeta [[, porque nunca houve um programa primordial chamado [[. Existe puramente dentro dos shells que o implementam como uma extensão do shell POSIX .

Parte da distinção entre "embutido" e "palavra-chave" se deve a esse histórico. Também reflete o fato de que as regras de sintaxe para analisar [[expressões são diferentes, como apontado na resposta de John1024.


Notas de rodapé:

  1. Quando você olha dessa maneira, fica claro por que você deve colocar espaços [nos scripts de shell, diferente da maneira como parênteses e colchetes funcionam na maioria das outras linguagens de programação. Se o analisador de comandos do shell permitisse if["$x"..., ele também teria que permitiriftest"$x"...

  2. Isso aconteceu por volta de 1980. /bin/[não existe na minha cópia do Ancient Unix V7 de 1979, nem a man testdocumenta como um pseudônimo. Na entrada da página de manual correspondente que eu tenho em uma cópia de pré-lançamento do manual do System III de 1980, ela está listada.

  3. ls -i /bin/[ /bin/test

  4. Mas não conte com esse comportamento. A versão Bash built-in de [requer o fechamento ], e sua built-in testaplicação vai reclamar se você não fornecê-la.

  5. A distinção de comando interno x externo também pode ser importante por outro motivo: as duas implementações podem se comportar de maneira diferente. Este é o caso echoem muitos sistemas. Como existe apenas uma implementação, essa distinção precisa ser feita para uma palavra-chave.

Warren Young
fonte
Obrigado @Warren Young, mas por que a distinção builtine keywordentre [e [[quando as duas oferecem a mesma funcionalidade (exceto pelo fato de que ela [[vem com mais recursos [)?
Sree
2
[[Sendo @sree uma palavra-chave, o bash pode fazer coisas que não são possíveis [- por exemplo, citar não é necessário muito tempo, porque o shell está ciente de que é uma variável. Ou seja, o processamento da linha de comando é afetado quando uma palavra-chave é usada, mas não quando um interno é usado - isso acontece muito mais tarde.
muru
11
Observe que o código para o shell V7 Bourne mostra um [built-in, mas o código está comentado.
Stéphane Chazelas
cdé um builtin, mas não oculta nada ( cdnão pode ser implementado como um programa externo).
Paŭlo Ebermann
2
Este é um excelente resumo histórico, mas deixa de fora os detalhes cruciais (IMO), apontados por muru acima e por John1024 em sua resposta, que criar [[uma palavra-chave permite que o shell use regras de análise especiais para seus argumentos. Assim, infelizmente, meu voto inicial vai para John1024.
Ilmari Karonen
3

[era originalmente apenas um comando externo, outro nome para /bin/test. Mas alguns comandos, como [e echo, são usados ​​com tanta frequência em scripts de shell que os implementadores de shell decidiram copiar o código diretamente no próprio shell, em vez de precisar executar outro processo toda vez que forem usados. Isso transformou esses comandos em "builtins", embora você ainda possa invocar o programa externo por todo o caminho.

[[veio muito mais tarde. Embora o builtin seja implementado internamente no shell, ele é analisado como comandos externos. Conforme explicado na resposta de John1024, isso significa que variáveis ​​não citadas receberão a divisão de palavras e tokens como >e <são processados ​​como redirecionamento de E / S. Isso tornou inconveniente escrever expressões complexas de comparação. [[foi criado como sintaxe do shell, para que pudesse ser analisado ideosincraticamente. Dentro de [[variáveis, não é possível dividir palavras, <e >pode ser usado como operadores de comparação, =pode se comportar de maneira diferente, dependendo se o próximo parâmetro é citado ou não, etc. Essas são todas as conveniências que tornam [[mais fácil o uso do que o [comando / builtin tradicional .

Eles não podiam simplesmente recodificar [como sintaxe como essa, porque teria sido uma alteração incompatível em milhões de scripts. Ao usar a nova [[sintaxe, que não existia anteriormente, eles poderiam renovar totalmente a maneira como é usada de uma maneira compatível para cima.

Isso é semelhante à evolução que resultou em $((...))sintaxe para expressões aritméticas, que substituíram principalmente o exprcomando tradicional .

Barmar
fonte
0

O mais recente [[em bashuma otimização de [.

O clássico [tem uma grande desvantagem, quando é usado com freqüência para fazer uma operação trivial: ele gera um novo processo toda vez:
(cria um novo espaço de endereço apenas para comparação 0e 1! Toda vez!)

Eu acho que um ponto principal da adição [[foi tornar a avaliação da expressão interna [não gerar um processo extra. Mas como o [trabalho não pode ser alterado - isso criaria muita confusão e problemas. Portanto, a otimização foi implementada com um novo nome, de forma mais eficiente, a saber, um comando embutido no shell.
Tornou-se palavra-chave na sintaxe do shell como efeito colateral.

No momento em que [foi usado primeiro, era o caminho certo para fazê-lo com um processo externo.

Volker Siegel
fonte
5
Observe que, embora [originalmente fosse um comando externo, ele foi adicionado ao shell bastante cedo, provavelmente no momento em que o Unix System III foi lançado e definitivamente antes do lançamento do Unix System V. Portanto, o 'processo extra' não é um problema há muito tempo. No entanto, a sintaxe permaneceu inalterada - foi tratada como se fosse um comando externo.
Jonathan Leffler
@JonathanLeffler Oh, obrigado, eu perdi que [é ao mesmo tempo - o que significa algumas mudanças ...
Volker Siegel