Por que a expansão de parâmetros com espaços sem aspas funciona entre colchetes duplos "[[", mas não entre colchetes únicos "["?

86

Estou confuso com o uso de colchetes simples ou duplos. Veja este código:

dir="/home/mazimi/VirtualBox VMs"

if [[ -d ${dir} ]]; then
    echo "yep"
fi

Funciona perfeitamente, embora a string contenha um espaço. Mas quando eu mudo para colchete único:

dir="/home/mazimi/VirtualBox VMs"

if [ -d ${dir} ]; then
    echo "yep"
fi

Diz:

./script.sh: line 5: [: /home/mazimi/VirtualBox: binary operator expected

Quando eu mudo para:

dir="/home/mazimi/VirtualBox VMs"

if [ -d "${dir}" ]; then
    echo "yep"
fi

Funciona bem. Alguém pode explicar o que está acontecendo? Quando devo atribuir aspas duplas em torno de variáveis "${var}"para evitar problemas causados ​​por espaços?

Majid Azimi
fonte
Uma pergunta mais genérica vs: unix.stackexchange.com/questions/306111/…
Ciro Santilli (

Respostas:

83

O colchete único [é na verdade um alias para o testcomando, não é uma sintaxe.

Uma das desvantagens (de muitas) do colchete único é que, se um ou mais operandos que ele está tentando avaliar retornar uma sequência vazia, ele reclamará que esperava dois operandos (binários). É por isso que você vê as pessoas [ x$foo = x$blah ], as xgarantias de que o operando nunca será avaliado como uma sequência vazia.

O colchete duplo [[ ]], por outro lado, é sintaxe e é muito mais capaz que [ ]. Como você descobriu, ele não possui o único problema de operando e também permite uma sintaxe semelhante a C com os >, <, >=, <=, !=, ==, &&, ||operadores.

Minha recomendação é a seguinte: Se o seu intérprete for #!/bin/bash, use sempre[[ ]]

É importante observar que isso [[ ]]não é suportado por todos os shells POSIX, no entanto, muitos shells o suportam, como zshe kshalém debash

SiegeX
fonte
16
O problema de cadeia vazia (e muitos outros) é resolvido usando aspas. O "x" é para cobrir outro tipo de problemas: onde operandos podem ser tomados como operadores . Como quando $fooé !ou (ou -n... Esse problema não deve ser um problema com shells POSIX, em que o número de argumentos (ao lado de [e ]) não é maior que quatro.
Stéphane Chazelas
21
Para esclarecer, [ x$foo = x$blah ]é tão errado quanto [ $foo = $bar ]. [ "$foo" = "$bar" ]está correto em qualquer shell compatível com POSIX, [ "x$foo" = "x$bar" ]iria trabalhar em qualquer shell Bourne-like, mas o xnão é para casos onde você tem strings vazias mas onde $foopodem estar !ou -n...
Stéphane Chazelas
1
Semântica interessante nesta resposta. Não sei ao certo o que você quer dizer com não é sintaxe . Claro, [não é exatamente um apelido para test. Se fosse, testaceitaria um colchete de fechamento. test -n foo ]. Mas testnão, e [requer um. [é idêntico testem todos os outros aspectos, mas não é assim que aliasfunciona. O Bash descreve [e testcomo componentes internos do shell , mas [[como uma palavra-chave do shell .
Kojiro # 9/17
1
Bem, no bash [é um shell embutido, mas / usr / bin / [também é um executável. Tradicionalmente, é um link para / usr / bin / test, mas no gnu moderno o coreutils é um binário separado. A versão tradicional do teste examina o argv [0] para ver se ele é chamado como [e, em seguida, procura a correspondência].
Evan
Meu bash não funciona com >=e<=
Student
52

O [comando é um comando comum. Embora a maioria dos shells o forneça como um built-in de eficiência, ela obedece às regras sintáticas normais do shell. [é exatamente equivalente a test, exceto que [requer a ]como seu último argumento e testnão.

Os colchetes duplos [[ … ]]são sintaxe especial. Eles foram introduzidos no ksh (vários anos depois [) porque [podem ser problemáticos de usar corretamente e [[permitem algumas novas adições interessantes que usam caracteres especiais do shell. Por exemplo, você pode escrever

[[ $x = foo && $y = bar ]]

porque toda a expressão condicional é analisada pelo shell, enquanto [ $x = foo && $y = bar ]que primeiro seria dividida em dois comandos [ $x = fooe $y = bar ]separada pelo &&operador. Da mesma forma, colchetes duplos permitem coisas como a sintaxe de correspondência de padrões, por exemplo, [[ $x == a* ]]para testar se o valor de xcomeça com a; entre colchetes, isso seria expandido a*para a lista de arquivos cujos nomes começam ano diretório atual. Os colchetes duplos foram introduzidos pela primeira vez no ksh e só estão disponíveis no ksh, bash e zsh.

Dentro de colchetes, você precisa usar aspas duplas em torno de substituições de variáveis, como na maioria dos outros lugares, porque são apenas argumentos para um comando (que por acaso é o [comando). Dentro de colchetes duplos, você não precisa de aspas duplas, porque o shell não divide ou brilha palavras: está analisando uma expressão condicional, não um comando.

Uma exceção, porém, é [[ $var1 = "$var2" ]]onde você precisa das aspas, se desejar fazer uma comparação de cadeias de bytes a bytes, caso contrário, $var2seria um padrão com o qual comparar $var1.

Uma coisa que você não pode fazer [[ … ]]é usar uma variável como operador. Por exemplo, isso é perfeitamente legal (mas raramente útil):

if [ -n "$reverse_sort" ]; then op=-gt; else op=-lt; fi

if [ "$x" "$op" "$y" ]; then 

No seu exemplo

dir="/home/mazimi/VirtualBox VMs"
if [ -d ${dir} ]; then 

o comando dentro do ifé [com os 4 argumentos -d, /home/mazimi/VirtualBox, VMse ]. O shell analisa -d /home/mazimi/VirtualBoxe depois não sabe o que fazer VMs. Você precisaria impedir a divisão de palavras ${dir}para obter um comando bem formado.

De um modo geral, sempre use aspas duplas em torno de substituições de variáveis ​​e comandos, a menos que você saiba que deseja executar a divisão e a observação de palavras no resultado. Os principais locais onde é seguro não usar aspas duplas são:

  • em uma atribuição: foo=$bar(mas observe que você precisa de aspas duplas em export "foo=$bar"ou em atribuições de matriz como array=("$a" "$b"));
  • em uma casedeclaração case $foo in …:;
  • dentro de colchetes duplos, exceto no lado direito da =ou ==operador (a menos que você deseja fazer a correspondência de padrões): [[ $x = "$y" ]].

Em tudo isso, é correto usar aspas duplas, assim você pode pular as regras avançadas e usá-las o tempo todo.

Gilles
fonte
1
@StephaneChazelas Um erro meu. Acontece que [é de fato do Sistema III em 1981 , pensei que era mais antigo.
Gilles
8

Quando devo atribuir aspas duplas em torno de variáveis "${var}"para evitar problemas causados ​​por espaços?

Implícito nesta pergunta é

Por que não é bom o suficiente?${variable_name}

${variable_name} não significa o que você pensa que faz ...

... se você acha que isso tem algo a ver com problemas causados ​​por espaços (em valores variáveis). é bom para isso:${variable_name}

$ bar=foo
$ bard=Shakespeare
$ echo $bard
Shakespeare
$ echo ${bar}d
food

e nada mais! 1  não faz qualquer bom a menos que você está seguindo-o imediatamente com um personagem que poderia ser parte de um nome de variável: uma carta (-ou-), um sublinhado (), ou um dígito (-). E mesmo assim, você pode contornar isso:${variable_name}AZaz_09

$ echo "$bar"d
food

Não estou tentando desencorajar seu uso - echo "${bar}d"provavelmente é a melhor solução aqui -, mas desencorajar as pessoas a confiarem em aparelhos em vez de aspas, ou a aplicar aparelhos instintivamente e depois perguntarem: “Agora, também preciso de aspas ? ”  Você deve sempre usar aspas menos que você tenha uma boa razão para não, e você tem certeza de que sabe o que está fazendo.
_________________
1    Exceto, é claro, o fato de que as formas mais sofisticadas de expansão de parâmetros , por exemplo, e se baseiam na sintaxe. Além disso, você precisa usar , etc. para referenciar os parâmetros 10, 11, etc., posicionais - as aspas não o ajudarão nisso.${parameter:-[word]}${parameter%[word]}${parameter}${10}${11}

G-Man
fonte
2

Para manipular espaços e caracteres especiais em branco nas variáveis, você deve sempre colocá-los entre aspas duplas. Definir um IFS adequado também é uma boa prática.

Recomendado: http://www.dwheeler.com/essays/filenames-in-shell.html

ramonovski
fonte