J'ai lu que je devrais citer des variables en bash, par exemple "$ foo" au lieu de $ foo. Cependant, lors de l'écriture d'un script, j'ai trouvé un cas où cela fonctionne sans guillemets mais pas avec eux:
wget_options='--mirror --no-host-directories'
local_root="$1" # ./testdir recieved from command line
remote_root="$2" # ftp://XXX recieved from command line
relative_path="$3" # /XXX received from command line
Celui-ci fonctionne:
wget $wget_options --directory_prefix="$local_root" "$remote_root$relative_path"
Ce n'est pas le cas (notez les guillemets doubles autour de $ wget_options):
wget "$wget_options" --directory_prefix="$local_root" "$remote_root$relative_path"
Quelle est la raison pour ça?
La première ligne est-elle la bonne version; ou devrais-je soupçonner qu'il y a une erreur cachée quelque part qui provoque ce comportement?
En général, où puis-je trouver une bonne documentation pour comprendre comment bash et ses citations fonctionnent? Lors de l'écriture de ce script, j'ai l'impression d'avoir commencé à travailler sur une base d'essais et d'erreurs au lieu de comprendre les règles.
wget
ne sait pas ce que--mirror --no-host-directories
signifie (comme un argument), mais il le gère lorsqu'il est divisé en deux arguments. Très peu de programmes traitent les espaces et les guillemets spécialement une fois qu'ils sont à l'intérieur du vecteur argument. Le problème est quebash
, et d'autres obus, sont censés être>bash
, vous pouvez donc imaginer que cela$a
équivaut exactement à écrire directement son contenu. Maintenant, le problème est évident:a="-a -b"; cmd "$a"
s'étend àcmd "-a -b"
, maiscmd
ne sait probablement pas ce que cela signifie.cmd $a
se développe pourcmd -a -b
, ce qui a probablement fait le travail.Réponses:
Fondamentalement, vous devez doubler les extensions de variable pour les protéger contre le fractionnement de mots (et la génération de noms de fichiers). Cependant, dans votre exemple,
le fractionnement de mots est exactement ce que vous voulez .
Avec
"$wget_options"
(cité),wget
ne sait pas quoi faire avec le seul argument--mirror --no-host-directories
et se plaintPour
wget
voir les deux options--mirror
et--no-host-directories
séparément, la séparation des mots doit se produire.Il existe des moyens plus robustes de procéder. Si vous utilisez
bash
ou tout autre shell qui utilise des tableaux commebash
do, consultez la réponse de glenn jackman . La réponse de Gilles décrit en outre une solution alternative pour les coques plus simples telles que la norme/bin/sh
. Les deux stockent essentiellement chaque option en tant qu'élément séparé dans un tableau.Question connexe avec de bonnes réponses: pourquoi mon script shell s'étouffe-t-il avec les espaces ou d'autres caractères spéciaux?
Les extensions de guillemets doubles sont une bonne règle de base. Fais ça . Soyez alors conscient des très rares cas où vous ne devriez pas faire cela. Ceux-ci se présenteront à vous par le biais de messages de diagnostic, tels que le message d'erreur ci-dessus.
Il y a aussi quelques cas où vous n'avez pas besoin de citer des extensions de variables. Mais il est plus facile de continuer à utiliser des guillemets, car cela ne fait pas beaucoup de différence. Un tel cas est
Un autre est
la source
$IFS
contient la bonne valeur. Ici, vous devez diviser l'espace et le texte ne contient aucun onglet ni nouvelle ligne, donc la valeur par défaut de$IFS
ferait l'affaire, mais si ce code doit être utilisé dans une fonction qui peut être appelée dans un contexte où il$IFS
aurait pu être modifié , vous voudriez le définir à l'$IFS
avance (et éventuellement le restaurer par la suite ou utiliser une portée locale pour celui-ci si le reste du code suppose un non modifié$IFS
)La façon la plus robuste de coder est d'utiliser un tableau:
la source
cp
etrsync
feront des choses inattendues si votre commande se développe en quelque chose commersync '' rest of parameters
. C'est idéal pour créer une commande pièce par pièce conditionnellement, puis l'exécuter une seule fois au même endroit.Vous essayez de stocker une liste de chaînes dans une variable de chaîne. Ça ne va pas. Peu importe comment vous accédez à la variable, quelque chose est cassé.
wget_options='--mirror --no-host-directories'
définit la variablewget_options
sur une chaîne qui contient un espace. À ce stade, il n'y a aucun moyen de savoir si l'espace est censé faire partie d'une option ou un séparateur entre les options.Lorsque vous accédez à la variable avec une substitution entre guillemets
wget "$wget_options"
, la valeur de la variable est utilisée comme chaîne. Cela signifie qu'il est transmis en tant que paramètre unique àwget
, c'est donc une seule option. Cela casse dans votre cas, car vous vouliez que cela signifie plusieurs options.Lorsque vous utilisez une substitution sans guillemets
wget $wget_options
, la valeur de la variable chaîne subit un processus d'expansion surnommé «split + glob»:$IFS
variable). Il en résulte une liste intermédiaire de chaînes.Cela se produit dans votre exemple, car le processus de fractionnement transforme l'espace en séparateur, mais ne fonctionne pas en général, car une option peut contenir des espaces et des caractères génériques.
Dans ksh, bash, yash et zsh, vous pouvez utiliser une variable de tableau. Un tableau en terminologie shell est une liste de chaînes, il n'y a donc pas de perte d'informations. Pour créer une variable de tableau, placez des parenthèses autour des éléments du tableau lors de l'attribution de la valeur à la variable. Pour accéder à tous les éléments du tableau, utilisez - ceci est une généralisation de , qui forme une liste à partir des éléments du tableau. Notez que vous avez également besoin des guillemets doubles, sinon chaque élément subit split + glob.
"${VARIABLE[@]}"
"$@"
En clair, il n'y a pas de variables de tableau. Si cela ne vous dérange pas de perdre les arguments positionnels, vous pouvez les utiliser pour stocker une liste de chaînes.
Pour plus d'informations, voir Pourquoi mon script shell s'étouffe-t-il sur les espaces ou d'autres caractères spéciaux?
la source
(set -- ...; exec wget "$@" ...)
.