Cela a été discuté dans un certain nombre de questions sur unix.SE, je vais essayer de rassembler tous les problèmes que je peux trouver ici. Références à la fin.
Pourquoi ça échoue
La raison pour laquelle vous rencontrez ces problèmes est le fractionnement de mots et le fait que les guillemets développés à partir de variables n'agissent pas comme des guillemets, mais sont juste des caractères ordinaires.
Les cas présentés dans la question:
$ abc='ls -l "/tmp/test/my dir"'
Ici, $abc
est divisé et ls
obtient les deux arguments "/tmp/test/my
et dir"
(avec les guillemets à l'avant du premier et à l'arrière du second):
$ $abc
ls: cannot access '"/tmp/test/my': No such file or directory
ls: cannot access 'dir"': No such file or directory
Ici, l'extension est citée, elle est donc conservée en un seul mot. Le shell essaie de trouver un programme appelé ls -l "/tmp/test/my dir"
, espaces et guillemets inclus.
$ "$abc"
bash: ls -l "/tmp/test/my dir": No such file or directory
Et ici, seul le premier mot ou $abc
est pris comme argument -c
, donc Bash s'exécute simplement ls
dans le répertoire courant. Les autres mots sont des arguments bash, et sont utilisés pour remplir $0
, $1
etc.
$ bash -c $abc
'my dir'
Avec bash -c "$abc"
, et eval "$abc"
, il y a une étape supplémentaire de traitement du shell, qui fait fonctionner les guillemets, mais entraîne également le nouveau traitement de toutes les extensions du shell , donc il y a un risque d'exécuter accidentellement une extension de commande à partir des données fournies par l'utilisateur, sauf si vous êtes très attention à citer.
De meilleures façons de le faire
Les deux meilleures façons de stocker une commande sont a) utiliser une fonction à la place, b) utiliser une variable de tableau (ou les paramètres positionnels).
Utilisation d'une fonction:
Déclarez simplement une fonction avec la commande à l'intérieur et exécutez la fonction comme s'il s'agissait d'une commande. Les extensions dans les commandes de la fonction ne sont traitées que lorsque la commande s'exécute, pas lorsqu'elle est définie, et vous n'avez pas besoin de citer les commandes individuelles.
# define it
myls() {
ls -l "/tmp/test/my dir"
}
# run it
myls
En utilisant un tableau:
Les tableaux permettent de créer des variables multi-mots où les mots individuels contiennent des espaces blancs. Ici, les mots individuels sont stockés en tant qu'éléments de tableau distincts et l' "${array[@]}"
expansion développe chaque élément en tant que mots shell séparés:
# define the array
mycmd=(ls -l "/tmp/test/my dir")
# run the command
"${mycmd[@]}"
La syntaxe est légèrement horrible, mais les tableaux vous permettent également de construire la ligne de commande pièce par pièce. Par exemple:
mycmd=(ls) # initial command
if [ "$want_detail" = 1 ]; then
mycmd+=(-l) # optional flag
fi
mycmd+=("$targetdir") # the filename
"${mycmd[@]}"
ou garder des parties de la ligne de commande constantes et utiliser le tableau en remplir seulement une partie, des options ou des noms de fichiers:
options=(-x -v)
files=(file1 "file name with whitespace")
target=/somedir
transmutate "${options[@]}" "${files[@]}" "$target"
L'inconvénient des tableaux est qu'ils ne sont pas une fonctionnalité standard, donc les shells POSIX simples (comme dash
, par défaut /bin/sh
dans Debian / Ubuntu) ne les prennent pas en charge (mais voir ci-dessous). Bash, ksh et zsh le font, cependant, il est donc probable que votre système ait un shell qui supporte les tableaux.
En utilisant "$@"
Dans les shells sans prise en charge des tableaux nommés, on peut toujours utiliser les paramètres de position (le pseudo-tableau "$@"
) pour contenir les arguments d'une commande.
Les éléments suivants doivent être des bits de script portables qui font l'équivalent des bits de code de la section précédente. Le tableau est remplacé par "$@"
, la liste des paramètres de position. Le réglage "$@"
se fait avec set
, et les guillemets autour "$@"
sont importants (ceux-ci entraînent la citation individuelle des éléments de la liste).
Tout d'abord, il suffit de stocker une commande avec des arguments dans "$@"
et de l'exécuter:
set -- ls -l "/tmp/test/my dir"
"$@"
Définition conditionnelle de parties des options de ligne de commande pour une commande:
set -- ls
if [ "$want_detail" = 1 ]; then
set -- "$@" -l
fi
set -- "$@" "$targetdir"
"$@"
Utiliser uniquement "$@"
pour les options et les opérandes:
set -- -x -v
set -- "$@" file1 "file name with whitespace"
set -- "$@" /somedir
transmutate "$@"
(Bien sûr, il "$@"
contient généralement les arguments du script lui-même, vous devrez donc les enregistrer quelque part avant de les redéfinir "$@"
.)
Soyez prudent avec eval
!
Comme eval
introduit un niveau supplémentaire de traitement des devis et d'expansion, vous devez être prudent avec la saisie de l'utilisateur. Par exemple, cela fonctionne tant que l'utilisateur ne tape pas de guillemets simples:
read -r filename
cmd="ls -l '$filename'"
eval "$cmd";
Mais s'ils donnent l'entrée '$(uname)'.txt
, votre script exécute joyeusement la substitution de commande.
Une version avec des tableaux est à l'abri de cela puisque les mots sont maintenus séparés pendant tout le temps, il n'y a pas de citation ou autre traitement pour le contenu de filename
.
read -r filename
cmd=(ls -ld -- "$filename")
"${cmd[@]}"
Les références