Por que '[' um shell embutido e '[[' uma palavra-chave do shell?

62

Até onde eu sei, [[ é uma versão aprimorada de [ , mas estou confuso quando vejo [[ como uma palavra-chave e [ sendo mostrado como um arquivo interno.

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

TLDP diz

A builtin may be a synonym to a system command of the same name, but Bash reimplements it internally. For example, the Bash echo command is not the same as /bin/echo, although their behavior is almost identical.

and

A keyword is a reserved word, token or operator. Keywords have a special meaning to the shell, and indeed are the building blocks of the shell's syntax. As examples, for, while, do, and ! are keywords. Similar to a builtin, a keyword is hard-coded into Bash, but unlike a builtin, a keyword is not in itself a command, but a subunit of a command construct. [2]

Isso não deve tornar os dois [ e [[ uma palavra-chave? Há algo que eu esteja sentindo falta aqui? Além disso, este link reafirma que [ e [[ devem pertencer ao mesmo tipo.

    
por Sree 09.02.2015 / 08:00

4 respostas

77

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

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

    [ -z $name ]
    

    O shell expandirá $name e executará ambos a divisão de palavras e geração de nome de arquivo no resultado, assim como 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 isso funcione corretamente, aspas são necessárias:

    $ [ -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 expandirá $name , mas, ao contrário de qualquer outro comando, ele não realizará nenhuma divisão de palavras nem geração de nome de arquivo no resultado. Por exemplo, o seguinte será bem-sucedido, apesar dos espaços incorporados em name :

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

Resumo

[ é 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 o trata especialmente e opera sob regras muito diferentes.

    
por 09.02.2015 / 08:38
61

Em V7 Unix - onde o shell Bourne fez sua estreia - [ foi chamado test , e existia apenas como /bin/test . Então, codifique 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 if usa para decidir o que fazer em seguida. Esse comando pode ser construído no shell ou pode ser um programa externo.¹

[ como uma alternativa para test veio 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 um .

[ e test podem ser implementados usando o mesmo código. Este é o caso de /bin/[ e /bin/test no OS X, onde estes são links físicos para o mesmo executável. ³ Como resultado, a implementação ignora completamente o ] à direita: ele não é necessário se você o chamar como /bin/[ e não reclama se fizer fornecer /bin/test .⁴

Nenhum desse histórico afeta [[ , porque nunca houve um programa primordial chamado [[ . Existe apenas dentro desses shells que o implementam como uma extensão do shell POSIX .

Parte da distinção entre "construído" e "palavra-chave" se deve a esse histórico. Isso também reflete o fato de que as regras de sintaxe para analisar [[ expressões são diferentes, conforme indicado na resposta de John1024 .⁵

Notas de rodapé:

  1. Quando você olha dessa maneira, fica claro por que você deve colocar espaços em torno de [ em scripts de shell, ao contrário do modo 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 permitir iftest"$x"...

  2. Aconteceu por volta de 1980. /bin/[ não existe na minha cópia do Antigo Unix V7 de 1979, nem o man test o documenta como um alias. Na entrada da página man correspondente, eu tenho em uma cópia de pré-lançamento do Manual do Sistema III de 1980, é listado.

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

  4. Mas não conte com esse comportamento. A versão interna do Bash de [ exige o fechamento de ] , e sua implementação test integrada irá reclamar se você fizer fornecê-la.

  5. A distinção entre comandos externos e internos também pode ser importante por outro motivo: as duas implementações podem se comportar de maneira diferente. Este é o caso de echo em muitos sistemas. Como há apenas uma implementação, essa distinção não precisa ser feita para uma palavra-chave.

por 09.02.2015 / 08:15
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 executar outro processo toda vez que são usados . Isso transformou esses comandos em "builtins", embora você ainda possa invocar o programa externo por meio de seu caminho completo.

[[ veio muito mais tarde. Embora o builtin seja implementado internamente dentro do shell, ele é analisado como comandos externos. Como explicado na resposta de John1024, isso significa que as variáveis não nomeadas obterão a divisão de palavras nelas e os tokens como > e < serão processados como redirecionamento de E / S. Isso tornou a escrita de expressões complexas de comparação inconvenientes. [[ foi criado como sintaxe do shell, para que pudesse ser analisado de forma idiossincrática. Dentro de variáveis [[ não obtêm divisão de palavras, < e > podem ser usados como operadores de comparação, = pode se comportar diferentemente dependendo se o próximo parâmetro é citado ou não, etc. Torne o [[ mais fácil de usar do que o tradicional [ command / builtin.

Eles não poderiam simplesmente recodificar [ como uma sintaxe como essa porque seria uma alteração incompatível em milhões de scripts. Ao usar a nova sintaxe [[ , que não existia anteriormente, eles poderiam reformular totalmente o modo como ela é usada de uma maneira compatível.

Isso é semelhante à evolução que resultou na sintaxe $((...)) para expressões aritméticas, que substituiu o comando tradicional expr .

    
por 11.02.2015 / 20:25
0

O mais recente [[ em bash é uma otimização de [ .

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

Acho que o ponto principal da adição de [[ foi fazer com que a avaliação da expressão dentro de [ não gerasse um processo extra. Mas como [ está funcionando não pode ser alterado - isso criaria muita confusão e problemas. Assim, a otimização foi implementada com um novo nome, de uma maneira mais eficiente, ou seja, um comando shell builtin. Tornou-se palavra-chave na sintaxe da shell como um efeito colateral.

No momento em que [ foi usado primeiro, foi o caminho certo para fazer isso com um processo externo.

    
por 09.02.2015 / 20:22