La règle générale dans les scripts shell est que les variables doivent toujours être citées sauf s'il existe une raison impérieuse de ne pas le faire. Pour plus de détails que vous ne voudriez probablement en savoir, jetez un œil à ce grand Q&R: implications de sécurité d'oublier de citer une variable dans des shells bash / POSIX .
Considérez cependant une fonction comme la suivante:
run_this(){
$@
}
Doit- $@
on y citer ou non? J'ai joué un peu avec et je n'ai trouvé aucun cas où le manque de guillemets a causé un problème. D'un autre côté, l'utilisation des guillemets le casse lors du passage d'une commande contenant des espaces en tant que variable entre guillemets:
#!/usr/bin/sh
set -x
run_this(){
$@
}
run_that(){
"$@"
}
comm="ls -l"
run_this "$comm"
run_that "$comm"
L'exécution du script ci-dessus renvoie:
$ a.sh
+ comm='ls -l'
+ run_this 'ls -l'
+ ls -l
total 8
-rw-r--r-- 1 terdon users 0 Dec 22 12:58 da
-rw-r--r-- 1 terdon users 45 Dec 22 13:33 file
-rw-r--r-- 1 terdon users 43 Dec 22 12:38 file~
+ run_that 'ls -l'
+ 'ls -l'
/home/terdon/scripts/a.sh: line 7: ls -l: command not found
Je peux contourner cela si j'utilise à la run_that $comm
place de run_that "$comm"
, mais comme la fonction run_this
(sans guillemets) fonctionne avec les deux, cela semble être le pari le plus sûr.
Donc, dans le cas spécifique de l'utilisation $@
dans une fonction dont le travail consiste à exécuter en $@
tant que commande, doit $@
être cité? Veuillez expliquer pourquoi il doit / ne doit pas être cité et donnez un exemple de données qui peuvent le casser.
la source
run_that
Le comportement est certainement ce à quoi je m'attendais (et s'il y a un espace sur le chemin de la commande?). Si vous vouliez l'autre comportement, sûrement vous le citez sur le site d' appel où vous savez quelles sont les données? Je m'attendrais à appeler cette fonction commerun_that ls -l
, ce qui fonctionne de la même manière dans l'une ou l'autre version. Y a-t-il un cas qui vous a fait vous attendre différemment?${mycmd[@]}
.Réponses:
Le problème réside dans la façon dont la commande est passée à la fonction:
"$@"
doit être utilisé dans le cas général où votrerun_this
fonction est préfixée à une commande normalement écrite.run_this
conduit à citer l'enfer:Je ne sais pas comment je dois passer un nom de fichier avec des espaces
run_this
.la source
run_this
.$@
citations accidentellement. J'aurais dû laisser un exemple. : Drun_this
. Il s'agit essentiellement du même problème que vous rencontrez avec le bourrage de commandes complexes dans des chaînes, comme indiqué dans la FAQ Bash 050 .C'est soit:
Ou:
ou:
Mais:
Cela n'a pas beaucoup de sens.
Si vous voulez exécuter la
ls -l
commande (pas lals
commande avecls
et-l
comme arguments), vous feriez:Mais si (plus probablement), c'est la
ls
commande avecls
et-l
comme arguments, vous exécuteriez:Maintenant, si c'est plus qu'une simple commande que vous voulez exécuter, si vous voulez faire des affectations de variables, des redirections, des tuyaux ..., seul
interpret_this_shell_code
fera:bien sûr, vous pouvez toujours faire:
la source
Le regarder du point de vue bash / ksh / zsh,
$*
et$@
sont un cas particulier de l'expansion générale des tableaux. Les extensions de tableau ne sont pas comme les extensions de variable normales:Avec les extensions
$*
/${a[*]}
vous obtenez le tableau joint avec la première valeur deIFS
- qui est l'espace par défaut - en une chaîne géante. Si vous ne le citez pas, il est divisé comme une chaîne normale.Avec les extensions
$@
/${a[@]}
, le comportement dépend du fait que l' extension$@
/${a[@]}
soit citée ou non:"$@"
ou"${a[@]}"
), vous obtenez l'équivalent de"$1" "$2" "$3" #...
ou"${a[1]}" "${a[2]}" "${a[3]}" # ...
$@
ou${a[@]}
) vous obtenez l'équivalent de$1 $2 $3 #...
ou${a[1]} ${a[2]} ${a[3]} # ...
Pour les commandes d'encapsulation, vous voulez certainement les extensions @ citées (1.).
Plus de bonnes informations sur les tableaux bash (et de type bash): https://lukeshu.com/blog/bash-arrays.html
la source
Depuis que vous ne doublez pas les guillemets
$@
, vous avez laissé tous les problèmes de globalisation dans le lien que vous avez donné à votre fonction.Comment pourriez-vous exécuter une commande nommée
*
? Vous ne pouvez pas le faire avecrun_this
:Et vous voyez, même lorsqu'une erreur s'est produite,
run_that
vous a donné un message plus significatif.La seule façon de développer
$@
des mots individuels est de les mettre entre guillemets. Si vous souhaitez l'exécuter en tant que commande, vous devez passer la commande et ses paramètres en tant que mots séparés. C'est ce que vous avez fait du côté de l'appelant, pas à l'intérieur de votre fonction.est un meilleur choix. Ou si votre tableau prend en charge les tableaux:
Même lorsque le shell ne prend pas du tout en charge le tableau, vous pouvez toujours jouer avec lui en utilisant
"$@"
.la source
L'exécution de variables dans
bash
est une technique sujette aux pannes. Il est tout simplement impossible d'écrire unerun_this
fonction qui gère correctement tous les cas de bord, comme:ls | grep filename
)ls > /dev/null
)if
while
etc.Si tout ce que vous voulez faire est d'éviter la répétition du code, il vaut mieux utiliser les fonctions. Par exemple, au lieu de:
Tu devrais écrire
Si les commandes ne sont disponibles qu'au moment de l'exécution, vous devez utiliser
eval
ce qui est spécifiquement conçu pour gérer toutes les bizarreries qui ferontrun_this
échouer:Notez que cela
eval
est connu pour des problèmes de sécurité, mais si vous transmettez des variables de sources non fiables àrun_this
, vous serez également confronté à l'exécution de code arbitraire.la source
Le choix t'appartient. Si vous ne citez
$@
aucune de ses valeurs, faites l' objet d'une expansion et d'une interprétation supplémentaires. Si vous le citez tous les arguments passés, la fonction est reproduite textuellement dans son expansion. Vous ne serez jamais en mesure de gérer de manière fiable les jetons de syntaxe du shell comme&>|
et etc de toute façon sans analyser les arguments vous-même de toute façon - et vous avez donc le choix plus raisonnable de remettre votre fonction l'une des deux:"$@"
....ou...
$@
.Aucune des deux façons n'est fausse si elle est intentionnelle et si les effets de ce que vous choisissez sont bien compris. Les deux méthodes présentent des avantages l'une par rapport à l'autre, bien que les avantages de la seconde soient rarement susceptibles d'être particulièrement utiles. Encore...
... ce n'est pas inutile , mais rarement susceptible d'être d'une grande utilité . Et dans un
bash
shell, parcebash
que par défaut ne colle pas une définition de variable à son environnement même lorsque ladite définition est ajoutée à la ligne de commande d'un builtin spécial ou à une fonction, la valeur globale de$IFS
n'est pas affectée et sa déclaration est locale uniquement à l'run_this()
appel.De même:
... le globbing est également configurable. Les citations ont un but - elles ne sont pas pour rien. Sans eux, l'expansion du shell subit une interprétation supplémentaire - une interprétation configurable . Auparavant - avec de très vieux shells - qui
$IFS
était globalement appliqué à toutes les entrées, et pas seulement aux extensions. En fait, lesdits obus se sont comportés de manière très similairerun_this()
à ce qu'ils ont brisé tous les mots saisis sur la valeur de$IFS
. Et donc, si ce que vous recherchez est ce comportement de shell très ancien, alors vous devriez l' utiliserrun_this()
.Je ne le cherche pas, et j'ai du mal à trouver un exemple utile pour le moment. Je préfère généralement que les commandes exécutées par mon shell soient celles que je tape dessus. Et donc, étant donné le choix, je le ferais presque toujours
run_that()
. Excepté...À peu près tout peut être cité. Les commandes s'exécuteront entre guillemets. Cela fonctionne car au moment où la commande est réellement exécutée, tous les mots d'entrée ont déjà subi la suppression des guillemets - qui est la dernière étape du processus d'interprétation des entrées du shell. Ainsi, la différence entre
'ls'
etls
ne peut avoir d'importance que lorsque le shell interprète - et c'est pourquoi la citationls
garantit qu'aucun alias nomméls
n'est substitué à monls
mot de commande cité . En dehors de cela, les seules choses affectées par les guillemets sont la délimitation des mots (qui explique comment et pourquoi la citation des variables / espaces blancs fonctionne) et l'interprétation des métacaractères et des mots réservés.Donc:
Vous ne pourrez jamais le faire avec
run_this()
ourun_that()
.Mais les noms de fonction, ou
$PATH
les commandes d , ou les commandes intégrées s'exécuteront très bien entre guillemets ou sans guillemets, et c'est exactement commentrun_this()
etrun_that()
fonctionner en premier lieu. Vous ne pourrez rien faire d'utile avec$<>|&(){}
aucun d'entre eux. À court deeval
, c'est.Mais sans cela, vous êtes contraint aux limites d'une simple commande en raison des guillemets que vous utilisez (même si vous ne le faites pas, car il
$@
agit comme une citation au début du processus lorsque la commande est analysée pour les métacaractères) . La même contrainte est vraie pour les affectations et les redirections de ligne de commande, qui sont limitées à la ligne de commande de la fonction. Mais ce n'est pas grave:J'aurais pu y
<
rediriger l'entrée ou la>
sortie aussi facilement que j'ai ouvert le tuyau.Quoi qu'il en soit, de manière détournée, il n'y a pas de bonne ou de mauvaise voie ici - chaque voie a ses utilisations. C'est juste que vous devez l'écrire comme vous avez l'intention de l'utiliser, et vous devez savoir ce que vous voulez faire. Citations Omettre peuvent avoir un but - sinon il n'y aurait pas être citations du tout - mais si vous les omettez pour des raisons non pertinentes à votre but, vous êtes juste à écrire du mauvais code. Faites ce que vous voulez dire; J'essaye de toute façon.
la source