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

Alguém poderia me dizer se devo ou não envolver aspas em torno de variables ​​em um script de shell?

Por exemplo, é o seguinte correto:

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

ou

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

E se sim, porque?

Regra geral: cite se pode estar vazia ou conter espaços (ou qualquer espaço em branco na verdade) ou caracteres especiais (curingas). Não citar cadeias de caracteres com espaços muitas vezes leva a que a shell desmembre um único argumento em muitos.

$? não precisa de cotações, pois é um valor numérico. O fato de o $URL precisar depende do que você permite lá e se você ainda quer um argumento se estiver vazio.

Eu costumo sempre citar as cordas apenas por hábito, já que é mais seguro assim.

Em resumo, cite tudo em que você não precisa do shell para executar a divisão de tokens e a expansão de curingas.

As aspas simples protegem o texto entre elas textualmente. É a ferramenta adequada quando você precisa garantir que o shell não toque na seqüência de caracteres. Normalmente, é o mecanismo de cotação de escolha quando você não precisa de 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 a interpolação de variables ​​é necessária. Com adaptações adequadas, também é uma boa solução quando você precisa de aspas simples na string. (Não existe uma maneira simples de escaping de uma aspa entre aspas simples, porque não há mecanismo de escape dentro de aspas simples – se houvesse, elas não citariam completamente na íntegra).

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

Nenhuma cotação é adequada quando você requer especificamente que o shell execute a divisão de tokens e / ou a expansão de curingas.

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 só é executado uma vez, sobre a única string citada.)

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

(O loop só é executado uma vez, sobre a string com aspas simples literal).

Expansão de 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á nenhum arquivo chamado 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 normalmente deve ser citada (porque os nomes de arquivos podem conter espaços em branco e outros metacaracteres de shell). Qualquer coisa que contenha uma URL deve normalmente ser citada (porque muitas URLs contêm metacaracteres de shell como ? E & ). Qualquer coisa que contenha uma regex geralmente deve ser citada (idem ditto). 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, de outra forma, o shell transformará os espaços em branco em espaços simples e compensará espaços em branco iniciais ou finais).

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

Valores que não são variables ​​seguem basicamente as mesmas regras, embora você possa então também escaping de quaisquer metacaracteres ao invés de citá-los. Para um exemplo comum, um URL com um & in será analisado pelo shell como um comando background, 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 a URL estiver em uma variável sem aspas.) Para uma string estática, aspas simples fazem mais sentido, embora qualquer forma de citar ou escaping 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”. Se você precisar misturar aspas simples e duplas, poderá usá-las adjacentes umas às outras. Por exemplo, as seguintes strings entre aspas

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

podem ser coladas juntas de costas, formando uma única string longa após a remoção de tokens e citações.

 $ 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 usar o ls para 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; o printf especificamente funciona bem com vários argumentos. stat também. Mas fazer um loop sobre uma correspondência de caractere curinga é um problema comum e frequentemente feito incorretamente.)

Uma variável contendo uma lista de tokens para fazer um loop ou um caractere curinga para expandir é vista com menos frequência, então, às vezes, abreviamos para "citar tudo, a menos que você saiba exatamente o que está fazendo".

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

Aspas duplas

Em contextos onde queremos suprimir a divisão de palavras e globbing. Também em contextos onde queremos que o literal seja tratado como uma string, não uma regex.

Aspas simples

Nos literais de string onde queremos suprimir a interpolação e o tratamento especial de barras invertidas. Em outras palavras, situações em que usar aspas duplas seria inadequado.

Sem citações

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


Exemplos

Aspas duplas

  • strings literais com espaço em branco ( "StackOverflow rocks!" , "Steve's Apple" )
  • expansões variables ​​( "$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/"* )
  • para proteger aspas simples ( "single'quote'delimited'string" )
  • Expansão do parâmetro Bash ( "${filename##*/}" )

Aspas simples

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

Sem citações

  • em torno de variables ​​numéricas padrão ( $$ , $? $# etc.)
  • em contextos aritméticos como ((count++)) , "${arr[idx]}" , "${string:start:length}"
  • dentro de [[ ]] expressão que é livre de problemas de divisão de palavras e globbing (isso é uma questão de estilo e as opiniões podem variar muito)
  • onde queremos dividir palavras ( for word in $words )
  • onde queremos globbing ( for txtfile in *.txt; do ... )
  • onde queremos que ~ seja interpretado como $HOME ( ~/"some dir" mas não "~/some dir" )

Veja também:

  • Diferença entre aspas simples e duplas no Bash
  • Quais são as variables ​​especiais do shell de cifrão?

Eu geralmente uso aspas como "$var" para segurança, a menos que eu tenha certeza de que $var não contém espaço.

Eu uso $var como 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