$ ls -l /tmp/test/my\ dir/
total 0
Je me demandais pourquoi les façons suivantes d'exécuter la commande ci-dessus échouent ou réussissent?
$ abc='ls -l "/tmp/test/my dir"'
$ $abc
ls: cannot access '"/tmp/test/my': No such file or directory
ls: cannot access 'dir"': No such file or directory
$ "$abc"
bash: ls -l "/tmp/test/my dir": No such file or directory
$ bash -c $abc
'my dir'
$ bash -c "$abc"
total 0
$ eval $abc
total 0
$ eval "$abc"
total 0
Réponses:
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:
Ici,
$abc
est divisé etls
obtient les deux arguments"/tmp/test/my
etdir"
(avec les guillemets à l'avant du premier et à l'arrière du second):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.Et ici, seul le premier mot ou
$abc
est pris comme argument-c
, donc Bash s'exécute simplementls
dans le répertoire courant. Les autres mots sont des arguments bash, et sont utilisés pour remplir$0
,$1
etc.Avec
bash -c "$abc"
, eteval "$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.
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: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:
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:
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 avecset
, 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:Définition conditionnelle de parties des options de ligne de commande pour une commande:
Utiliser uniquement
"$@"
pour les options et les opérandes:(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: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
.Les références
la source
cmd="ls -l $(printf "%q" "$filename")"
. pas joli, mais si l'utilisateur est prêt à utiliser uneval
, cela aide. Il est également très utile pour envoyer la commande si des choses semblables, commessh foohost "ls -l $(printf "%q" "$filename")"
, ou dans l'esprit de cette question:ssh foohost "$cmd"
.$ alias abc='ls -l "/tmp/test/my dir"'
La manière la plus sûre d'exécuter une commande (non triviale) est
eval
. Ensuite, vous pouvez écrire la commande comme vous le feriez sur la ligne de commande et elle est exécutée exactement comme si vous veniez de la saisir. Mais vous devez tout citer.Cas simple:
cas pas si simple:
la source
Le deuxième signe de citation rompt la commande.
Quand je cours:
Cela m'a donné une erreur.
Mais quand je cours
Il n'y a aucune erreur du tout
Il n'y a aucun moyen de résoudre ce problème pour le moment (pour moi), mais vous pouvez éviter l'erreur en n'ayant pas d'espace dans le nom du répertoire.
Cette réponse a dit que la commande eval peut être utilisée pour résoudre ce problème mais cela ne fonctionne pas pour moi :(
la source
Si cela ne fonctionne pas
''
, vous devez utiliser``
:Mettre à jour mieux:
la source
$( command... )
au lieu de backticks.Que diriez-vous d'un one-liner python3?
la source