web-dev-qa-db-fra.com

Quand mettre des citations autour d'une variable Shell?

Quelqu'un pourrait-il me dire si je devrais ou non entourer des citations autour de variables dans un script Shell?

Par exemple, la réponse suivante est-elle correcte:

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

ou

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

Et si oui, pourquoi?

133
Cristian

Règle générale: citez-la si elle peut être vide ou contenir des espaces (ou tout espace réellement) ou des caractères spéciaux (caractères génériques). Ne pas citer de chaînes avec des espaces amène souvent le shell à diviser un seul argument en plusieurs.

$? n'a pas besoin de guillemets puisqu'il s'agit d'une valeur numérique. Qu'il s'agisse $URL _ besoin dépend de ce que vous autorisez et si vous voulez toujours un argument s'il est vide.

J'ai tendance à toujours citer les chaînes juste par habitude, car c'est plus sûr.

94
paxdiablo

En résumé, citez tout ce qui ne nécessite pas que le shell effectue le fractionnement de jetons et le développement de caractères génériques.

Les guillemets simples protègent le texte entre eux. C'est l'outil approprié lorsque vous devez vous assurer que le shell ne touche pas du tout la corde. En règle générale, c'est le mécanisme de citation de choix lorsque vous ne nécessitez pas d'interpolation de variable.

$ 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.

Les guillemets doubles conviennent lorsqu'une interpolation variable est requise. Avec des adaptations appropriées, c'est également une bonne solution de contournement lorsque vous avez besoin de guillemets simples dans la chaîne. (Il n’existe aucun moyen simple d’échapper une citation unique entre guillemets simples, car il n’existe aucun mécanisme d’échappement entre guillemets simples; s’il existait, ils ne citeraient pas complètement.)

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

Aucun guillemets ne convient lorsque vous avez spécifiquement besoin que le shell effectue le fractionnement de jetons et/ou le développement de caractères génériques.

Fractionnement de jetons;

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

Par contre:

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

(La boucle ne s'exécute qu'une fois, par-dessus la seule chaîne citée.)

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

(La boucle ne s'exécute qu'une fois, par-dessus la chaîne littérale citant une seule citation.)

Expansion de Wildcard:

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

Par contre:

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

(Il n'y a pas de fichier nommé littéralement file*.txt.)

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

(Il n'y a pas de fichier nommé $pattern, non plus!)

Plus concrètement, tout ce qui contient un nom de fichier doit généralement être cité (car les noms de fichiers peuvent contenir des espaces et d’autres métacaractères Shell). Tout ce qui contient une URL doit généralement être cité (car de nombreuses URL contiennent des métacaractères Shell tels que ? et &). Tout ce qui contient une regex devrait normalement être cité (idem idem). Tout ce qui contient des espaces significatifs autres que des espaces simples entre des caractères non-blancs doit être indiqué (sinon, le Shell transmettra les espaces blancs en un seul espace et supprimera les espaces blancs précédents ou suivants).

Lorsque vous savez qu'une variable ne peut contenir qu'une valeur qui ne contient pas de métacaractères Shell, la citation est facultative. Ainsi, un non coté $? est fondamentalement correct, car cette variable ne peut contenir qu'un seul nombre. Toutefois, "$?" est également correct et recommandé pour des raisons de cohérence et d’exactitude (bien que ce soit ma recommandation personnelle et non une politique largement reconnue).

Les valeurs qui ne sont pas des variables suivent essentiellement les mêmes règles, mais vous pouvez également échapper aux métacaractères au lieu de les citer. Pour un exemple courant, une URL avec un & dans celui-ci sera analysé par le shell comme une commande d’arrière-plan, sauf si le métacaractère est échappé ou cité:

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

(Bien entendu, cela se produit également si l'URL est dans une variable non entre guillemets.) Pour une chaîne statique, les guillemets simples sont les plus utiles, bien que toute forme de guillemet ou d'échappement fonctionne ici.

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

Le dernier exemple suggère également un autre concept utile, que j'aime appeler "citation de bascule". Si vous devez mélanger des guillemets simples et doubles, vous pouvez les utiliser adjacents. Par exemple, les chaînes citées suivantes

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

peuvent être collés dos à dos, formant une seule longue chaîne après la suppression des jetons et des devis.

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

Ce n’est pas très lisible, mais c’est une technique courante et donc utile à connaître.

En passant, les scripts ne devraient généralement pas utiliser ls pour quoi que ce soit. Pour développer un caractère générique, il suffit de ... l'utiliser.

$ 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

(La boucle est totalement superflue dans le dernier exemple; printf fonctionne très bien avec plusieurs arguments. stat aussi. Mais boucler une correspondance avec un caractère générique est un problème courant, et souvent mal effectué.)

Une variable contenant une liste de jetons à boucler ou un caractère générique à développer est moins fréquemment vue. Nous abrégons donc parfois pour "citer tout sauf si vous savez exactement ce que vous faites".

71
tripleee

Voici une formule en trois points pour les citations en général:

guillemets doubles

Dans les contextes où nous voulons supprimer la division et la suppression de mots de Word. Également dans des contextes où nous voulons que le littéral soit traité comme une chaîne, pas comme une expression régulière.

guillemets simples

Dans les littéraux de chaîne où nous souhaitons supprimer l’interpolation et le traitement spécial des barres obliques inverses. En d'autres termes, l'utilisation de guillemets doubles serait inappropriée.

Pas de guillemets

Dans les contextes où nous sommes absolument sûrs qu'il n'y a pas de problèmes de fractionnement ou de globalisation de mots ou que nous voulons le fractionnement et le déplacement de Word.


Exemples

guillemets doubles

  • chaînes littérales avec des espaces ("StackOverflow rocks!", "Steve's Apple")
  • extensions variables ("$var", "${arr[@]}")
  • substitutions de commandes ("$(ls)", "`ls`")
  • globs où le chemin du répertoire ou la partie du nom de fichier contient des espaces ("/my dir/"*)
  • protéger les guillemets simples ("single'quote'delimited'string")
  • Expansion du paramètre Bash ("${filename##*/}")

guillemets simples

  • noms de commandes et arguments contenant des espaces
  • chaînes littérales nécessitant la suppression de l'interpolation ('Really costs $$!', 'just a backslash followed by a t: \t')
  • pour protéger les guillemets doubles ('The "crux"')
  • littéraux de regex nécessitant une interpolation pour être supprimés
  • utilisez les citations Shell pour les littéraux impliquant des caractères spéciaux ($'\n\t')
  • utilisez Shell en citant les endroits où nous devons protéger plusieurs guillemets simples et doubles ($'{"table": "users", "where": "first_name"=\'Steve\'}')

Pas de guillemets

  • autour des variables numériques standard ($$, $?, $# etc.)
  • dans des contextes arithmétiques comme ((count++)), "${arr[idx]}", "${string:start:length}"
  • à l'intérieur d'une expression [[ ]] ne contenant pas de problèmes de fractionnement ni de globalisation de mots (ceci est une question de style et d'opinion peut varier considérablement)
  • où nous voulons la division de Word (for Word in $words)
  • où nous voulons globbing (for txtfile in *.txt; do ...)
  • où nous voulons que ~ soit interprété comme $HOME (~/"some dir" mais pas "~/some dir")

Voir également:

16
codeforester

J'utilise généralement cité comme "$var" pour la sécurité, sauf si je suis sûr que $var ne contient pas d'espace.

J'utilise $var comme moyen simple de joindre des lignes:

lines="`cat multi-lines-text-file.txt`"
echo "$lines"                             ## multiple lines
echo $lines                               ## all spaces (including newlines) are zapped
1
Bach Lien