$ ls -l /tmp/test/my\ dir/ total 0
Je me demandais pourquoi les méthodes suivantes pour 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
Commentaires
- mywiki.wooledge.org/BashFAQ / 050
- Implications sur la sécurité de loubli de citer une variable dans les shells bash / POSIX – Mais que faire si…?
Réponse
Ceci a été discuté dans un certain nombre de questions sur unix.SE, jessaierai de tout rassembler problèmes que je peux trouver ici. Références à la fin.
Pourquoi cela échoue
La raison pour laquelle vous rencontrez ces problèmes est fractionnement de mots et le fait que les guillemets développés à partir de variables nagissent pas comme des guillemets, mais ne sont que des caractères ordinaires.
Le cas présentés dans la question:
Laffectation ici attribue la seule chaîne ls -l "/tmp/test/my dir"
à abc
:
$ abc="ls -l "/tmp/test/my dir""
Ci-dessous, $abc
est divisé en espace et ls
obtient les trois arguments -l
, "/tmp/test/my
et dir"
(avec un guillemet à lavant du deuxième et un autre à larrière du troisième). Loption fonctionne, mais le chemin nest pas traité correctement:
$ $abc ls: cannot access ""/tmp/test/my": No such file or directory ls: cannot access "dir"": No such file or directory
Ici, lexpansion est entre guillemets, elle est donc conservée comme un seul mot. Le shell essaie pour trouver un programme appelé littéralement 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, $abc
est divisé, et seul le premier mot résultant est pris comme argument de -c
, donc Bash exécute simplement ls
dans le répertoire courant. Les autres mots sont des arguments de 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 un étape de traitement du shell, qui fait fonctionner les guillemets, mais provoque également le nouveau traitement de toutes les extensions du shell , donc « un risque dexécuter accidentellement par exemple une substitution de commande à partir de données fournies par lutilisateur, sauf si vous » re très soin
De meilleures façons de le faire
Les deux meilleures façons de stocker une commande sont a) dutiliser une fonction à la place, b) dutiliser une variable de tableau ( ou les paramètres de position).
Utilisation dune fonction:
Déclarez simplement une fonction avec la commande à lintérieur, et exécutez la fonction comme sil sagissait dune commande. Les extensions des commandes au sein de la fonction ne sont traitées que lorsque la commande sexécute, pas lorsquelle est définie, et vous navez pas besoin de citer les commandes individuelles.
# define it myls() { ls -l "/tmp/test/my dir" } # run it myls
Utilisation dun tableau:
Les tableaux permettent de créer des variables multi-mots où les mots individuels contiennent du blanc espacer. Ici, les mots individuels sont stockés en tant quéléments de tableau distincts et le développement "${array[@]}"
étend 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 un peu 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 conservez des parties de la ligne de commande constantes et utilisez le tableau pour nen remplir quune partie, comme des options ou des noms de fichiers:
options=(-x -v) files=(file1 "file name with whitespace") target=/somedir transmutate "${options[@]}" "${files[@]}" "$target"
Linconvénient des tableaux est quils « ne sont pas une fonctionnalité standard, donc des shells POSIX simples (comme dash
, la valeur par défaut /bin/sh
dans Debian / Ubuntu) ne les supporte pas (mais voir ci-dessous). Cependant, Bash, ksh et zsh le font, il est donc probable que votre système dispose dun shell prenant en charge les tableaux.
Utilisation de "$@"
Dans les shells sans support pour les tableaux nommés, on peut toujours utiliser les paramètres positionnels (le pseudo-tableau "$@"
) pour contenir les arguments dune commande.
Ce qui suit doit ê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 de "$@"
se fait avec set
, et les guillemets doubles autour de "$@"
sont importants (ceux-ci font que les éléments de la liste sont cités individuellement).
Tout dabord, il suffit de stocker une commande avec des arguments dans "$@"
et de lexécuter:
set -- ls -l "/tmp/test/my dir" "$@"
Définition conditionnelle de certaines parties des options de ligne de commande pour une commande:
set -- ls if [ "$want_detail" = 1 ]; then set -- "$@" -l fi set -- "$@" "$targetdir" "$@"
Utilisation de "$@"
uniquement pour les options et les opérandes :
set -- -x -v set -- "$@" file1 "file name with whitespace" set -- "$@" /somedir transmutate "$@"
(Bien sûr, "$@"
est généralement rempli avec les arguments du script lui-même, donc vous » Vous devrez les enregistrer quelque part avant de réutiliser "$@"
.)
Faites attention avec eval
!
Comme eval
introduit un niveau supplémentaire de traitement des devis et des extensions, vous devez être prudent avec les saisies de lutilisateur. Par exemple, cela fonctionne tant que lutilisateur ne saisissez pas de guillemets simples:
read -r filename cmd="ls -l "$filename"" eval "$cmd";
Mais sils donnent lentrée "$(uname)".txt
, votre script sera heureux exécute la substitution de commande.
Une version avec des tableaux est immunisée contre le comme les mots sont séparés pendant tout le temps, il ny a pas de guillemet ou autre traitement pour le contenu de filename
.
read -r filename cmd=(ls -ld -- "$filename") "${cmd[@]}"
Références
- Division de mots dans BashGuide
- BashFAQ / 050 ou » Jessaye de mettre une commande dans une variable, mais les cas complexes échouent toujours! »
- La question Pourquoi mon script shell sétouffe avec des espaces ou dautres caractères spéciaux? , qui traite dun certain nombre de problèmes liés aux guillemets et aux espaces, y compris le stockage des commandes.
Commentaires
- vous pouvez contourner le problème des citations eval en faisant
cmd="ls -l $(printf "%q" "$filename")"
. pas joli, mais si lutilisateur est déterminé à utiliser uneval
, cela aide. Il ‘ est également très utile pour envoyer la commande via des choses similaires, telles quessh foohost "ls -l $(printf "%q" "$filename")"
, ou dans lesprit de cette question:ssh foohost "$cmd"
. - Pas directement lié, mais avez-vous codé le répertoire en dur? Dans ce cas, vous voudrez peut-être regarder lalias. Quelque chose comme:
$ alias abc='ls -l "/tmp/test/my dir"'
Answer
Le moyen le plus sûr de 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 il faut tout citer.
Cas simple:
abc="ls -l "/tmp/test/my dir"" eval "$abc"
cas pas si simple:
# command: awk "! a[$0]++ { print "foo: " $0; }" inputfile abc="awk "\""! a[$0]++ { print "foo: " $0; }"\"" inputfile" eval "$abc"
Commentaires
- Bonjour @HaukeLaging, quest-ce que \ ‘ ‘?
- @jumping_monkey Vous ne pouvez pas avoir de
'
dans une chaîne entre guillemets'
. Ainsi, vous devez (1) terminer / interrompre la citation, (2) échapper au'
suivant pour lobtenir littéralement, (3) taper la citation littérale et (4) dans cas dune interruption reprenez la chaîne entre guillemets:(1)'(2)\(3)'(4)'
- Bonjour @HaukeLaging, intéressant, et pourquoi pas juste
abc='awk \'! a[$0]++ { print "foo: " $0; }\' inputfile'
? - @jumping_monkey Sans parler du fait que je viens de vous expliquer pourquoi cela ne fonctionne pas: ‘ t il serait logique de tester le code avant de le publier?
Réponse
Le deuxième signe de guillemet casse la commande.
Quand je lance:
abc="ls -l "/home/wattana/Desktop"" $abc
Cela ma donné une erreur.
Mais quand je lance
abc="ls -l /home/wattana/Desktop" $abc
Il ny a aucune erreur du tout
Il ny a aucun moyen de résoudre ce problème pour le moment (pour moi) mais vous pouvez éviter lerreur en nayant pas despace dans le nom du répertoire.
Cette réponse indique que la commande eval peut être utilisée pour résoudre ce problème, mais cela ne fonctionne pas pour moi 🙁
Commentaires
- Ouais, cela fonctionne tant que ‘ nest pas nécessaire, par exemple noms de fichiers avec des espaces incorporés (ou ceux contenant des caractères glob).
Réponse
Si cela ne fonctionne pas avec ""
, alors vous devriez utiliser ``
:
abc=`ls -l /tmp/test/my\ dir/`
Mieux mettre à jour manière:
abc=$(ls -l /tmp/test/my\ dir/)
Commentaires
- Ceci stocke le résultat de la commande dans une variable. LOP souhaite (étrangement) enregistrer la commande elle-même dans une variable. Oh, et vous devriez vraiment commencer à utiliser
$( command... )
au lieu des backticks. - Merci beaucoup pour les clarifications et les conseils!
Réponse
Que diriez-vous dun python3 one-liner?
bash-4.4# pwd /home bash-4.4# CUSTOM="ls -altr" bash-4.4# python3 -c "import subprocess; subprocess.call(\"$CUSTOM\", shell=True)" total 8 drwxr-xr-x 2 root root 4096 Mar 4 2019 . drwxr-xr-x 1 root root 4096 Nov 27 16:42 ..
Commentaires
- La question concerne BASH, pas python.
Réponse
Une autre astuce simple pour exécuter nimporte quelle commande (triviale / non triviale) stockée dans abc
variable est:
$ history -s $abc
et appuyez sur UpArrow
ou Ctrl-p
pour lafficher dans la ligne de commande. Contrairement à toute autre méthode de cette façon, vous pouvez le modifier avant lexécution si nécessaire.
Cette commande ajoutera le contenu de la variable en tant que nouvelle entrée à lhistorique de Bash et vous pourrez le rappeler par UpArrow
.