Quando é necessário aspas duplas?

120

O conselho antigo costumava ser o de citar duas vezes qualquer expressão que envolva a $VARIABLE, pelo menos se alguém quisesse que ela fosse interpretada pelo shell como um único item; caso contrário, qualquer espaço no conteúdo de $VARIABLEisso jogaria fora o shell.

Entendo, no entanto, que em versões mais recentes de shells, as aspas duplas nem sempre são necessárias (pelo menos para o objetivo descrito acima). Por exemplo, em bash:

% FOO='bar baz'
% [ $FOO = 'bar baz' ] && echo OK
bash: [: too many arguments
% [[ $FOO = 'bar baz' ]] && echo OK
OK
% touch 'bar baz'
% ls $FOO
ls: cannot access bar: No such file or directory
ls: cannot access baz: No such file or directory

Por zshoutro lado, os mesmos três comandos são bem-sucedidos. Portanto, com base nesse experimento, parece que, em bash, é possível omitir aspas duplas dentro [[ ... ]], mas não dentro [ ... ]nem em argumentos de linha de comando, enquanto que, em zsh, as aspas duplas podem ser omitidas em todos esses casos.

Mas inferir regras gerais a partir de exemplos anedóticos, como o acima, é uma proposição arriscada. Seria bom ver um resumo de quando as aspas duplas são necessárias. Estou principalmente interessado em zsh, bashe /bin/sh.

kjo
fonte
10
Seu comportamento observado no zsh depende das configurações e é influenciado pela SH_WORD_SPLITopção.
Ulrich Dangel
3
Como um aparte - os nomes de variáveis ​​all-caps são usados ​​por variáveis ​​com significado para o sistema operacional e o shell; a especificação POSIX aconselha explicitamente o uso de nomes em minúsculas para variáveis ​​definidas pelo aplicativo. (Enquanto a especificação citada se concentra especificamente em variáveis ​​de ambiente, variáveis ​​de ambiente e variáveis ​​de shell compartilham um espaço para nome: Tentar criar uma variável de shell com um nome já usado por uma variável de ambiente substitui a última). Veja pubs.opengroup.org/onlinepubs/009695399/basedefs/… , quarto parágrafo.
Charles Duffy

Respostas:

128

Primeiro, separe o zsh do resto. Não se trata de conchas antigas e modernas: o zsh se comporta de maneira diferente. Os designers do zsh decidiram torná-lo incompatível com os shells tradicionais (Bourne, ksh, bash), mas mais fáceis de usar.

Segundo, é muito mais fácil usar aspas duplas o tempo todo do que lembrar quando elas são necessárias. Eles são necessários na maioria das vezes, portanto, você precisará aprender quando não forem necessários, não quando forem necessários.

Em poucas palavras, aspas duplas são necessárias sempre que uma lista de palavras ou um padrão é esperado . Eles são opcionais em contextos em que uma sequência bruta é esperada pelo analisador.

O que acontece sem aspas

Observe que, sem aspas duplas, duas coisas acontecem.

  1. Primeiro, o resultado da expansão (o valor da variável para uma substituição de parâmetro como ${foo}, ou a saída do comando para uma substituição de comando como $(foo)) é dividido em palavras onde quer que haja espaço em branco.
    Mais precisamente, o resultado da expansão é dividido em cada caractere que aparece no valor da IFSvariável (caractere separador). Se uma sequência de caracteres separadores contiver espaços em branco (espaço, tabulação ou nova linha), o espaço em branco será contabilizado como um único caractere; separadores à esquerda, à esquerda ou repetidos em branco não levam a campos vazios. Por exemplo, with IFS=" :", :one::two : three: :four produz campos vazios antes one, entre onee two, e (um único) entre threee four.
  2. Cada campo resultante da divisão é interpretado como um glob (um padrão curinga) se ele contiver um dos caracteres \[*?. Se esse padrão corresponder a um ou mais nomes de arquivos, o padrão será substituído pela lista de nomes de arquivos correspondentes.

Uma expansão de variável não citada $fooé conhecida coloquialmente como "operador split + glob", em contraste com o "$foo"qual apenas assume o valor da variável foo. O mesmo vale para substituição de comando: "$(foo)"é uma substituição de comando, $(foo)é uma substituição de comando seguida por split + glob.

Onde você pode omitir as aspas duplas

Aqui estão todos os casos em que posso pensar em um shell no estilo Bourne, no qual você pode escrever uma substituição de variável ou comando sem aspas duplas, e o valor é interpretado literalmente.

  • No lado direito de uma tarefa.

    var=$stuff
    a_single_star=*
    

    Observe que você precisa de aspas duplas depois export, porque é um builtin comum, não uma palavra-chave. Isso é verdade apenas em alguns shells, como dash, zsh (emulação sh), yash ou chique; o bash e o ksh tratam exportespecialmente.

    export VAR="$stuff"
  • Em uma casedeclaração.

    case $var in 

    Observe que você precisa de aspas duplas em um padrão de caso. A divisão de palavras não ocorre em um padrão de maiúsculas e minúsculas, mas uma variável entre aspas é interpretada como um padrão, enquanto uma variável entre aspas é interpretada como uma sequência literal.

    a_star='a*'
    case $var in
      "$a_star") echo "'$var' is the two characters a, *";;
       $a_star) echo "'$var' begins with a";;
    esac
    
  • Dentro de colchetes duplos. Parênteses duplos são sintaxe especial do shell.

    [[ -e $filename ]]

    Exceto que você precisa de aspas duplas onde é esperado um padrão ou expressão regular: no lado direito de =ou ==ou !=ou =~.

    a_star='a*'
    if [[ $var == "$a_star" ]]; then echo "'$var' is the two characters a, *"
    elif [[ $var == $a_star ]]; then echo "'$var' begins with a"
    fi
    

    Você precisa de aspas duplas, como de costume, entre colchetes, [ … ]porque elas são sintaxe comum do shell (é um comando que é chamado [). Consulte Parênteses simples ou duplos

  • Em um redirecionamento em shells POSIX não interativos (não bash, nem ksh88).

    echo "hello world" >$filename

    Algumas shells, quando interativas, tratam o valor da variável como um padrão curinga. O POSIX proíbe esse comportamento em shells não interativos, mas alguns shells, incluindo o bash (exceto no modo POSIX) e o ksh88 (inclusive quando encontrado como o (supostamente) POSIX shde alguns Unices comerciais como o Solaris) ainda o fazem lá ( bashtambém tentam dividir e o redirecionamento falhará, a menos que a divisão + oclusão resulte em exatamente uma palavra), e é por isso que é melhor citar destinos de redirecionamentos em um shscript, caso você queira convertê-lo em um bashscript algum dia ou executá-lo em um sistema onde shestá não compatível nesse ponto ou pode ser proveniente de shells interativos.

  • Dentro de uma expressão aritmética. De fato, você precisa deixar as aspas para que uma variável seja analisada como uma expressão aritmética.

    expr=2*2
    echo "$(($expr))"
    

    No entanto, você precisa das aspas em torno da expansão aritmética, pois elas estão sujeitas à divisão de palavras na maioria dos shells, conforme o POSIX exige (!?).

  • Em uma matriz associativa subscrita.

    typeset -A a
    i='foo bar*qux'
    a[foo\ bar\*qux]=hello
    echo "${a[$i]}"
    

Uma variável não citada e substituição de comando podem ser úteis em algumas circunstâncias raras:

  • Quando o valor da variável ou a saída do comando consiste em uma lista de padrões globais e você deseja expandir esses padrões para a lista de arquivos correspondentes.
  • Quando você sabe que o valor não contém nenhum caractere curinga, isso $IFSnão foi modificado e você deseja dividi-lo em caracteres de espaço em branco.
  • Quando você deseja dividir um valor em um determinado caractere: desative o globbing com set -f, defina IFSo caractere separador (ou deixe em branco para dividir no espaço em branco) e faça a expansão.

Zsh

No zsh, você pode omitir as aspas duplas na maioria das vezes, com algumas exceções.

  • $varnunca se expande para várias palavras, no entanto, se expande para a lista vazia (em oposição a uma lista que contém uma única palavra vazia) se o valor de varfor a sequência vazia. Contraste:

    var=
    print -l $var foo        # prints just foo
    print -l "$var" foo      # prints an empty line, then foo
    

    Da mesma forma, se "${array[@]}"expande para todos os elementos da matriz, enquanto se $arrayexpande apenas para os elementos não vazios.

  • A @bandeira de expansão de parâmetros, por vezes, requer aspas em torno toda a substituição: "${(@)foo}".

  • A substituição de comando sofre divisão de campo se não estiver entre aspas: echo $(echo 'a'; echo '*')imprime a *(com um único espaço) enquanto echo "$(echo 'a'; echo '*')"imprime a sequência de duas linhas não modificada. Use "$(somecommand)"para obter a saída do comando em uma única palavra, sem novas linhas finais. Use "${$(somecommand; echo _)%?}"para obter a saída exata do comando, incluindo novas linhas finais. Use "${(@f)$(somecommand)}"para obter uma matriz de linhas da saída do comando.

Gilles
fonte
De fato, você precisa deixar as aspas para que uma variável seja analisada como uma expressão aritmética. Por que eu sou capaz de fazer o seu exemplo funcionar com aspas:echo "$(("$expr"))"
Cyker
É o que man bashdiz: A expressão é tratada como se estivesse entre aspas duplas, mas uma aspas dupla entre parênteses não é tratada especialmente.
29316 Cyker #
4
Além disso, para quem estiver interessado, os nomes formais de split + glob são divisão de palavras e expansão de nome de caminho .
Cyker
3
Para sua informação - no StackOverflow , alguém solicitou a linguagem "opcional quando se espera uma sequência de caracteres brutos" nesta resposta para defender não citar um argumento echo. Pode valer a pena tentar tornar a linguagem ainda mais explícito ( "quando uma corda crua é esperado pelo analisador", talvez?)
Charles Duffy
2
@CharlesDuffy Ugh, eu não tinha pensado nessa leitura errada. Mudei "onde" para "quando" e reforcei a frase como você sugeriu.
Gilles