Quando agrupar aspas em torno de uma variável de shell?

184

Alguém poderia me dizer se devo ou não colocar aspas em torno de variáveis ​​em um script de shell?

Por exemplo, o seguinte está correto:

xdg-open $URL 
[ $? -eq 2 ]

ou

xdg-open "$URL"
[ "$?" -eq "2" ]

E se sim, por quê?

Cristian
fonte
Esta pergunta obtém muitas duplicatas, muitas das quais não são sobre variáveis, então, retomei o valor "value" em vez de "variable". Espero que isso ajude mais pessoas a encontrar esse tópico.
tripleee
1
@codeforester O que há com a edição revertida?
Tripleee
1
Relacionado: Diferença entre aspas simples e duplas no Bash também.
codeforester

Respostas:

131

Regra geral: cite se pode estar vazio ou conter espaços (ou qualquer espaço em branco) ou caracteres especiais (curingas). Não citar cadeias de caracteres com espaços geralmente leva o shell a dividir um único argumento em muitos.

$?não precisa de aspas, pois é um valor numérico. Se $URLprecisa, depende do que você permite e se ainda deseja uma discussão, se estiver vazia.

Costumo sempre citar strings apenas por hábito, pois é mais seguro assim.

paxdiablo
fonte
2
Observe que "espaços" realmente significa "qualquer espaço em branco".
William Pursell
4
@ Cristian: Se você não tem certeza do que pode estar na variável, é mais seguro citá-la. Tenho a tendência de seguir o mesmo princípio que paxdiablo e apenas criar o hábito de citar tudo (a menos que haja um motivo específico para não fazê-lo).
Gordon Davisson
11
Se você não souber o valor do IFS, cite-o, não importa o quê. Se IFS=0, então echo $?pode ser muito surpreendente.
Charles Duffy
3
Cite com base no contexto, não no que você espera que os valores sejam, caso contrário, seus erros serão piores. Por exemplo, você tem certeza de que nenhum de seus caminhos possui espaços, portanto, você pode escrever cp $source1 $source2 $dest, mas se por algum motivo inesperado destnão for definido, o terceiro argumento simplesmente desaparecerá e ele será copiado silenciosamente, source1em source2vez de fornecer um erro apropriado para o destino em branco (como teria se você tivesse citado cada argumento).
Derek Veit
3
quote it if...tem o processo de pensamento para trás - as aspas não são algo que você adiciona quando precisa, são algo que você remove quando precisa. Sempre envolver cordas e scripts entre aspas simples a menos que você precisa para usar aspas duplas (por exemplo, para permitir que uma variável expandir) ou necessidade de usar sem aspas (por exemplo, para fazer englobamento eo nome do arquivo de expansão).
Ed Morton
92

Em resumo, cite tudo onde você não precisa que o shell execute a divisão de token e a expansão de curinga.

Aspas simples protegem o texto entre elas literalmente. É a ferramenta adequada quando você precisa garantir que o shell não toque na corda. Normalmente, é o mecanismo de citação preferido quando você não requer interpolação variável.

$ echo 'Nothing \t in here $will change'
Nothing \t in here $will change

$ grep -F '@&$*!!' file /dev/null
file:I can't get this @&$*!! quoting right.

Aspas duplas são adequadas quando é necessária interpolação variável. Com adaptações adequadas, também é uma boa solução alternativa quando você precisa de aspas simples na string. (Não existe uma maneira simples de escapar de uma citação única entre aspas simples, porque não há mecanismo de escape dentro de aspas simples - se houvesse, elas não citariam completamente textualmente.)

$ echo "There is no place like '$HOME'"
There is no place like '/home/me'

Nenhuma cotação é adequada quando você exige especificamente que o shell execute a divisão de token e / ou expansão de curinga.

Divisão de token;

 $ words="foo bar baz"
 $ for word in $words; do
 >   echo "$word"
 > done
 foo
 bar
 baz

Por contraste:

 $ for word in "$words"; do echo "$word"; done
 foo bar baz

(O loop é executado apenas uma vez, sobre a única cadeia de caracteres citada.)

 $ for word in '$words'; do echo "$word"; done
 $words

(O loop é executado apenas uma vez, sobre a string literal entre aspas.)

Expansão curinga:

$ pattern='file*.txt'
$ ls $pattern
file1.txt      file_other.txt

Por contraste:

$ ls "$pattern"
ls: cannot access file*.txt: No such file or directory

(Não há arquivo nomeado literalmente file*.txt.)

$ ls '$pattern'
ls: cannot access $pattern: No such file or directory

(Não há arquivo chamado $pattern !)

Em termos mais concretos, qualquer coisa que contenha um nome de arquivo geralmente deve ser citada (porque os nomes de arquivos podem conter espaços em branco e outros metacaracteres do shell). Qualquer coisa que contenha uma URL geralmente deve ser citada (porque muitas URLs contêm metacaracteres de shell como ?e& ). Qualquer coisa que contenha uma regex geralmente deve ser citada (idem idem). Qualquer coisa que contenha espaços em branco significativos que não sejam espaços únicos entre caracteres que não sejam espaços em branco precisa ser citada (porque, caso contrário, o shell moverá o espaço em branco para, efetivamente, espaços únicos e aparará qualquer espaço em branco à esquerda ou à direita).

Quando você sabe que uma variável pode conter apenas um valor que não contém metacaracteres de shell, a citação é opcional. Assim, um não citado $?é basicamente bom, porque essa variável só pode conter um único número. Contudo,"$?" também é correto e recomendado para consistência e correção gerais (embora essa seja minha recomendação pessoal, não uma política amplamente reconhecida).

Os valores que não são variáveis ​​seguem basicamente as mesmas regras, embora você também possa escapar de qualquer metacaractere em vez de citá-los. Para um exemplo comum, um URL com um &in será analisado pelo shell como um comando em segundo plano, a menos que o metacaractere seja escapado ou citado:

$ wget http://example.com/q&uack
[1] wget http://example.com/q
-bash: uack: command not found

(Obviamente, isso também acontece se o URL estiver em uma variável sem aspas.) Para uma sequência estática, aspas simples fazem mais sentido, embora qualquer forma de citação ou escape funcione aqui.

wget 'http://example.com/q&uack'  # Single quotes preferred for a static string
wget "http://example.com/q&uack"  # Double quotes work here, too (no $ or ` in the value)
wget http://example.com/q\&uack   # Backslash escape
wget http://example.com/q'&'uack  # Only the metacharacter really needs quoting

O último exemplo também sugere outro conceito útil, que eu gosto de chamar de "gangorra citando". Se você precisar misturar aspas simples e duplas, poderá usá-las adjacentes uma à outra. Por exemplo, as seguintes seqüências de caracteres citadas

'$HOME '
"isn't"
' where `<3'
"' is."

podem ser colados juntos, seguidos, formando uma única sequência longa após a remoção de tokenização e cotação.

$ echo '$HOME '"isn't"' where `<3'"' is."
$HOME isn't where `<3' is.

Isso não é muito legível, mas é uma técnica comum e, portanto, é bom saber.

Como um aparte, os scripts geralmente não devem ser usados lspara nada. Para expandir um curinga, apenas ... use-o.

$ printf '%s\n' $pattern   # not ``ls -1 $pattern''
file1.txt
file_other.txt

$ for file in $pattern; do  # definitely, definitely not ``for file in $(ls $pattern)''
>  printf 'Found file: %s\n' "$file"
> done
Found file: file1.txt
Found file: file_other.txt

(O loop é completamente supérfluo no último exemplo; printffunciona especificamente bem com vários argumentos.stat . Mas repetir uma correspondência curinga é um problema comum e frequentemente incorreto.)

Uma variável que contém uma lista de tokens a serem repetidos ou um curinga a ser expandido é vista com menos frequência; portanto, às vezes abreviamos para "citar tudo, a menos que você saiba exatamente o que está fazendo".

triplo
fonte
1
Essa é uma variante de (parte de) uma resposta que eu postei em uma pergunta relacionada . Estou colando aqui, porque isso é sucinto e bem definido o suficiente para se tornar uma pergunta canônica para esse problema em particular.
Tripleee
4
Observarei que este é o item 0 e um tema recorrente na coleção de erros comuns do Bash mywiki.wooledge.org/BashPitfalls . Muitos dos itens individuais dessa lista são basicamente sobre esse problema.
Tripleee
27

Aqui está uma fórmula de três pontos para citações em geral:

Aspas duplas

Em contextos em que queremos suprimir a divisão de palavras e globbing. Também em contextos em que queremos que o literal seja tratado como uma sequência, não como uma expressão regular.

Citações simples

Em literais de cadeia de caracteres onde queremos suprimir a interpolação e tratamento especial de barras invertidas. Em outras palavras, situações em que o uso de aspas duplas seriam inapropriadas.

Sem aspas

Em contextos em que temos certeza absoluta de que não há problemas de divisão ou globbing de palavras, ou queremos a divisão e globbing de palavras .


Exemplos

Aspas duplas

  • cadeias literais com espaço em branco ( "StackOverflow rocks!", "Steve's Apple")
  • expansões variáveis ​​( "$var", "${arr[@]}")
  • substituições de comandos ( "$(ls)", "`ls`")
  • globs em que o caminho do diretório ou a parte do nome do arquivo inclui espaços ( "/my dir/"*)
  • proteger aspas simples ( "single'quote'delimited'string")
  • Expansão do parâmetro Bash ( "${filename##*/}")

Citações simples

  • nomes e argumentos de comando que possuem espaço em branco
  • cadeias literais que precisam de interpolação para serem suprimidas ( 'Really costs $$!', 'just a backslash followed by a t: \t')
  • proteger aspas duplas ( 'The "crux"')
  • literais regex que precisam de interpolação para serem suprimidos
  • use shell citar para literais envolvendo caracteres especiais ( $'\n\t')
  • use aspas onde precisamos proteger várias aspas simples e duplas ( $'{"table": "users", "where": "first_name"=\'Steve\'}')

Sem aspas

  • numéricos variáveis em torno padrão ( $$, $?, $#etc.)
  • em contextos aritméticas gosto ((count++)), "${arr[idx]}","${string:start:length}"
  • [[ ]]expressão interna isenta de divisão de palavras e problemas de globbing (isso é uma questão de estilo e as opiniões podem variar bastante)
  • onde queremos dividir palavras ( for word in $words)
  • onde queremos globbing ( for txtfile in *.txt; do ...)
  • onde queremos ~ser interpretados como $HOME( ~/"some dir"mas não "~/some dir")

Veja também:

codeforester
fonte
3
De acordo com essas diretrizes, é possível obter uma lista de arquivos no diretório raiz, escrevendo "ls" "/" A frase "todos os contextos de string" precisa ser qualificada com mais cuidado.
William Pursell
5
Em [[ ]], citar importa no lado direito de =/ ==e =~: faz a diferença entre interpretar uma string como um padrão / regex ou literalmente.
Benjamin W.
6
Uma boa visão geral, mas vale a pena integrar os comentários do @ BenjaminW $'...'.
mklement0
3
@ mklement0, na verdade eles são equivalentes. Essas diretrizes indicam que você deve sempre digitar em "ls" "/"vez das mais comuns ls /, e considero isso uma falha importante nas diretrizes.
William Pursell
4
Para sem aspas que você pode adicionar atribuição de variável ou case:)
PesaThe
4

Eu geralmente uso citado como "$var"para seguro, a menos que tenha certeza de que $varnão contém espaço.

Eu uso $varcomo uma maneira simples de juntar linhas:

lines="`cat multi-lines-text-file.txt`"
echo "$lines"                             ## multiple lines
echo $lines                               ## all spaces (including newlines) are zapped
Bach Lien
fonte
O comentário final é um pouco enganador; as novas linhas são efetivamente substituídas por espaços, não simplesmente removidas.
Tripleee 10/10
-1

Para usar as variáveis ​​no script de shell, use as variáveis ​​citadas "" como a citada significa que a variável pode conter espaços ou caracteres especiais que não afetarão a execução do seu script de shell. Caso contrário, se você tiver certeza de não ter espaços ou caracteres especiais no nome da sua variável, poderá usá-los sem "".

Exemplo:

eco "$ url name" - (pode ser usado o tempo todo)

eco "$ url name" - (não pode ser usado em tais situações, tome cuidado antes de usá-lo)

Vipul Sharma
fonte