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?
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.
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".
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
"StackOverflow rocks!"
, "Steve's Apple"
)"$var"
, "${arr[@]}"
)"$(ls)"
, "`ls`"
)"/my dir/"*
)"single'quote'delimited'string"
)"${filename##*/}"
)guillemets simples
'Really costs $$!'
, 'just a backslash followed by a t: \t'
)'The "crux"'
)$'\n\t'
)$'{"table": "users", "where": "first_name"=\'Steve\'}'
)Pas de guillemets
$$
, $?
, $#
etc.)((count++))
, "${arr[idx]}"
, "${string:start:length}"
[[ ]]
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)for Word in $words
)for txtfile in *.txt; do ...
)~
soit interprété comme $HOME
(~/"some dir"
mais pas "~/some dir"
)Voir également:
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