web-dev-qa-db-fra.com

Conséquences pour la sécurité d'oublier de citer une variable dans des shells bash / POSIX

Si vous suivez unix.stackexchange.com depuis un certain temps, vous devriez maintenant espérer savoir que laisser une variable sans guillemets dans le contexte d'une liste (comme dans echo $var) dans les shells Bourne/POSIX (zsh étant l'exception) a une signification très spéciale et ne devrait être fait que si vous avez une très bonne raison de le faire.

Il est longuement discuté dans un certain nombre de questions et réponses ici (Exemples: Pourquoi mon script Shell s'étouffe-t-il sur les espaces ou d'autres caractères spéciaux? , Quand est-il nécessaire de citer deux fois? , - Expansion d'une variable Shell et effet de glob et division sur elle , Expansion de chaîne entre guillemets et non entre guillemets )

Cela a été le cas depuis la sortie initiale du Bourne Shell à la fin des années 70 et n'a pas été modifié par le Korn Shell (l'un des les plus grands regrets de David Korn (question # 7) ) ou bash qui a principalement copié le Korn Shell, et c'est ainsi que cela a été spécifié par POSIX/Unix.

Maintenant, nous voyons toujours un certain nombre de réponses ici et même parfois du code Shell publiquement publié où les variables ne sont pas citées. Vous auriez pensé que les gens auraient appris maintenant.

D'après mon expérience, il existe principalement 3 types de personnes qui omettent de citer leurs variables:

  • débutants. Ceux-ci peuvent être excusés car il est vrai que c'est une syntaxe complètement peu intuitive. Et c'est notre rôle sur ce site de les éduquer.

  • les gens oublieux.

  • les gens qui ne sont pas convaincus même après avoir martelé à plusieurs reprises, qui pensent que sûrement l'auteur Bourne Shell n'avait pas l'intention de nous citer toutes nos variables .

Peut-être pourrons-nous les convaincre si nous exposons le risque associé à ce genre de comportements.

Quelle est la pire chose qui puisse arriver si vous oubliez de citer vos variables. Est-ce vraiment que mauvais?

De quel type de vulnérabilité parle-t-on ici?

Dans quels contextes cela peut-il être un problème?

214
Stéphane Chazelas

Préambule

Tout d'abord, je dirais que ce n'est pas la bonne façon de régler le problème. C'est un peu comme dire " tu ne devrais pas tuer des gens parce que sinon tu iras en prison".

De même, vous ne citez pas votre variable car sinon vous introduisez des failles de sécurité. Vous citez vos variables parce qu'il est faux de ne pas le faire (mais si la peur de la prison peut vous aider, pourquoi pas).

Un petit résumé pour ceux qui viennent de sauter dans le train.

Dans la plupart des shells, laisser une expansion variable sans guillemets (bien que (et le reste de cette réponse) s'applique également à la substitution de commande (`...` Ou $(...)) et à l'expansion arithmétique ($((...)) ou $[...])) a une signification très spéciale. La meilleure façon de le décrire est que c'est comme invoquer une sorte d'opérateur implicite split + glob¹.

cmd $var

dans une autre langue serait écrit quelque chose comme:

cmd(glob(split($var)))

$var Est d'abord divisé en une liste de mots selon des règles complexes impliquant le paramètre spécial $IFS (La partie split) puis chaque mot résultant de ce fractionnement est considéré comme un motif qui est étendu à une liste de fichiers qui lui correspondent (la partie glob).

Par exemple, si $var Contient *.txt,/var/*.xml Et $IFS Contient ,, cmd sera appelé avec un certain nombre d'arguments, le premier l'un étant cmd et les suivants étant les fichiers txt dans le répertoire courant et les fichiers xml dans /var.

Si vous vouliez appeler cmd avec juste les deux arguments littéraux cmd et *.txt,/var/*.xml, Vous écririez:

cmd "$var"

qui serait dans votre autre langue plus familière:

cmd($var)

Qu'entendons-nous par vulnérabilité dans un Shell?

Après tout, il est connu depuis la nuit des temps que les scripts Shell ne doivent pas être utilisés dans des contextes sensibles à la sécurité. Certes, OK, laisser une variable sans guillemets est un bug, mais cela ne peut pas faire autant de mal, n'est-ce pas?

Eh bien, malgré le fait que quiconque vous dirait que les scripts Shell ne devraient jamais être utilisés pour les CGI Web, ou que, heureusement, la plupart des systèmes n'autorisent pas les scripts Shell setuid/setgid de nos jours, une chose que Shellshock (le bogue bash exploitable à distance qui a fait en septembre 2014) a révélé que les shells sont toujours largement utilisés là où ils ne devraient probablement pas: dans les CGI, dans les scripts de hook du client DHCP, dans les commandes sudoers, invoqués par (sinon as) Commandes setuid ...

Parfois sans le savoir. Par exemple system('cmd $PATH_INFO') dans un php/Perl/python script CGI appelle un Shell pour interpréter cette ligne de commande (sans parler du fait que cmd lui-même peut être un script Shell et son auteur ne s'est peut-être jamais attendu à ce qu'il soit appelé à partir d'un CGI).

Vous avez une vulnérabilité quand il y a un chemin pour une élévation de privilèges, c'est quand quelqu'un (appelons-le l'attaquant) est capable de faire quelque chose qu'il n'est pas censé faire.

Cela signifie invariablement l'attaquant fournir des données, ces données étant traitées par un utilisateur/processus privilégié qui fait par inadvertance quelque chose qu'il ne devrait pas faire, dans la plupart des cas à cause d'un bogue.

En gros, vous avez un problème lorsque votre code buggy traite des données sous le contrôle de l'attaquant.

Maintenant, il n'est pas toujours évident d'où ces données peuvent provenir, et il est souvent difficile de dire si votre code pourra jamais traiter des données non fiables.

En ce qui concerne les variables, dans le cas d'un script CGI, c'est assez évident, les données sont les paramètres CGI GET/POST et des choses comme les cookies, le chemin, les paramètres Host ...

Pour un script setuid (exécuté en tant qu'un utilisateur lorsqu'il est appelé par un autre), ce sont les arguments ou les variables d'environnement.

Les noms de fichiers sont un autre vecteur très courant. Si vous obtenez une liste de fichiers à partir d'un répertoire, il est possible que des fichiers y aient été plantés par l'attaquant.

À cet égard, même à l'invite d'un shell interactif, vous pourriez être vulnérable (lors du traitement de fichiers dans /tmp Ou ~/tmp Par exemple).

Même un ~/.bashrc Peut être vulnérable (par exemple, bash l'interprétera lorsqu'il sera invoqué sur ssh pour exécuter un ForcedCommand comme dans git déploiements de serveurs avec quelques variables sous le contrôle du client).

Désormais, un script ne peut pas être appelé directement pour traiter des données non fiables, mais il peut être appelé par une autre commande qui le fait. Ou votre code incorrect peut être copié-collé dans des scripts qui le font (par vous 3 ans plus tard ou l'un de vos collègues). Un endroit où c'est particulièrement critique est dans les réponses dans les sites Q&R car vous ne saurez jamais où les copies de votre code peuvent se retrouver.

Aux affaires; est-ce mauvais?

Laisser une variable (ou substitution de commande) sans guillemets est de loin la principale source de vulnérabilités de sécurité associées au code Shell. En partie parce que ces bogues se traduisent souvent par des vulnérabilités, mais aussi parce qu'il est si courant de voir des variables non citées.

En fait, lors de la recherche de vulnérabilités dans le code Shell, la première chose à faire est de rechercher les variables non cotées. Il est facile à repérer, souvent un bon candidat, généralement facile à retrouver dans les données contrôlées par l'attaquant.

Il existe un nombre infini de façons dont une variable non citée peut se transformer en vulnérabilité. Je vais juste donner ici quelques tendances communes.

Divulgation d'information

La plupart des gens se heurteront à des bogues associés à des variables non cotées en raison de la partie split (par exemple, il est courant que les fichiers aient des espaces dans leurs noms de nos jours et l'espace est dans la valeur par défaut d'IFS). Beaucoup de gens négligeront la partie glob. La partie glob est au moins aussi dangereuse que la partie split.

Globbing effectué sur une entrée externe non assainie signifie l'attaquant peut vous faire lire le contenu d'un répertoire.

Dans:

echo You entered: $unsanitised_external_input

si $unsanitised_external_input contient /*, cela signifie l'attaquant peut voir le contenu de /. Pas grave. Cela devient cependant plus intéressant avec /home/* Qui vous donne une liste de noms d'utilisateurs sur la machine, /tmp/*, /home/*/.forward Pour des conseils sur d'autres pratiques dangereuses, /etc/rc*/* pour les services activés ... Pas besoin de les nommer individuellement. Une valeur de /* /*/* /*/*/*... Affichera simplement l'ensemble du système de fichiers.

Vulnérabilités de déni de service.

Prenant le cas précédent un peu trop loin et nous avons un DoS.

En fait, toute variable non cotée dans un contexte de liste avec une entrée non-autorisée est au moins une vulnérabilité DoS.

Même les scripteurs Shell experts oublient souvent de citer des choses comme:

#! /bin/sh -
: ${QUERYSTRING=$1}

: Est la commande no-op. Qu'est ce qui pourrait aller mal?

Cela est destiné à affecter $1 À $QUERYSTRING Si $QUERYSTRING N'était pas défini. C'est également un moyen rapide de rendre un script CGI appelable à partir de la ligne de commande.

Cependant, $QUERYSTRING Est encore développé et comme il n'est pas cité, l'opérateur split + glob est invoqué.

Maintenant, il y a des globes dont l'expansion est particulièrement coûteuse. Le /*/*/*/* Est assez mauvais car cela signifie lister les répertoires jusqu'à 4 niveaux plus bas. En plus de l'activité du disque et du CPU, cela signifie stocker des dizaines de milliers de chemins de fichiers (40k ici sur une VM de serveur minimale, dont 10k de répertoires).

Maintenant /*/*/*/*/../../../../*/*/*/* Signifie 40k x 10k et /*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/* Est suffisant pour mettre à genoux même la machine la plus puissante.

Essayez-le par vous-même (mais préparez-vous à ce que votre machine tombe en panne ou se bloque):

a='/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*' sh -c ': ${a=foo}'

Bien sûr, si le code est:

echo $QUERYSTRING > /some/file

Ensuite, vous pouvez remplir le disque.

Faites juste une recherche Google sur Shell cgi ou bash cgi ou ksh cgi , et vous trouverez quelques pages qui vous montreront comment écrire CGI dans des coquilles. Remarquez comment la moitié de ceux qui traitent les paramètres sont vulnérables.

Même celui de David Korn est vulnérable (regardez la gestion des cookies).

jusqu'aux vulnérabilités d'exécution de code arbitraire

L'exécution de code arbitraire est le pire type de vulnérabilité, car si l'attaquant peut exécuter n'importe quelle commande, il n'y a aucune limite à ce qu'il peut faire.

C'est généralement la partie split qui y mène. Ce fractionnement entraîne la transmission de plusieurs arguments aux commandes lorsqu'un seul est attendu. Alors que les premiers seront utilisés dans le contexte attendu, les autres seront dans un contexte différent donc potentiellement interprétés différemment. Mieux avec un exemple:

awk -v foo=$external_input '$2 == foo'

Ici, l'intention était d'assigner le contenu de la variable Shell $external_input À la variable fooawk.

Maintenant:

$ external_input='x BEGIN{system("uname")}'
$ awk -v foo=$external_input '$2 == foo'
Linux

Le deuxième mot résultant de la division de $external_input N'est pas affecté à foo mais considéré comme awk code (ici qui exécute une commande arbitraire: uname).

C'est particulièrement un problème pour les commandes qui peuvent exécuter d'autres commandes (awk, env, sed (GNU one), Perl, find ...) en particulier avec les variantes GNU (qui acceptent les options après les arguments). Parfois, vous ne soupçonneriez pas les commandes de pouvoir en exécuter d'autres comme ksh, bash ou zsh[ ou printf...

for file in *; do
  [ -f $file ] || continue
  something-that-would-be-dangerous-if-$file-were-a-directory
done

Si nous créons un répertoire appelé x -o yes, Alors le test devient positif, car c'est une expression conditionnelle complètement différente que nous évaluons.

Pire, si nous créons un fichier appelé x -a a[0$(uname>&2)] -gt 1, avec toutes les implémentations ksh au moins (qui inclut le sh de la plupart des Unices commerciaux et certains BSD), qui exécute uname parce que ces shells effectuent une évaluation arithmétique sur les opérateurs de comparaison numérique de la commande [.

$ touch x 'x -a a[0$(uname>&2)] -gt 1'
$ ksh -c 'for f in *; do [ -f $f ]; done'
Linux

Idem avec bash pour un nom de fichier comme x -a -v a[0$(uname>&2)].

Bien sûr, s'ils ne peuvent pas obtenir une exécution arbitraire, l'attaquant peut se contenter de dommages moindres (ce qui peut aider à obtenir une exécution arbitraire). Toute commande qui peut écrire des fichiers ou modifier les autorisations, la propriété ou avoir un effet principal ou secondaire pourrait être exploitée.

Toutes sortes de choses peuvent être faites avec des noms de fichiers.

$ touch -- '-R ..'
$ for file in *; do [ -f "$file" ] && chmod +w $file; done

Et vous finissez par rendre .. Accessible en écriture (récursivement avec GNU chmod).

Les scripts effectuant le traitement automatique de fichiers dans des zones accessibles en écriture comme /tmp Doivent être écrits très soigneusement.

Qu'en est-il de [ $# -gt 1 ]

C'est quelque chose que je trouve exaspérant. Certaines personnes s'efforcent de se demander si une expansion particulière peut être problématique pour décider si elles peuvent omettre les guillemets.

C'est comme dire. Hé, il semble que $# Ne puisse pas être soumis à l'opérateur split + glob, demandons au Shell de le split + glob. Ou Hé, écrivons du code incorrect juste parce que le bogue est peu susceptible d'être atteint.

Maintenant, est-ce peu probable? OK, $# (Ou $!, $? Ou toute substitution arithmétique) ne peut contenir que des chiffres (ou - Pour certains) donc le glob la pièce est sortie. Pour que la partie split fasse quelque chose, tout ce dont nous avons besoin est que $IFS Contienne des chiffres (ou -).

Avec certains shells, $IFS Peut être hérité de l'environnement, mais si l'environnement n'est pas sûr, c'est fini quand même.

Maintenant, si vous écrivez une fonction comme:

my_function() {
  [ $# -eq 2 ] || return
  ...
}

Cela signifie que le comportement de votre fonction dépend du contexte dans lequel elle est appelée. Ou en d'autres termes, $IFS Devient l'une des entrées de celui-ci. À strictement parler, lorsque vous écrivez la documentation de l'API pour votre fonction, cela devrait ressembler à:

# my_function
#   inputs:
#     $1: source directory
#     $2: destination directory
#   $IFS: used to split $#, expected not to contain digits...

Et le code appelant votre fonction doit s'assurer que $IFS Ne contient pas de chiffres. Tout ça parce que vous n'aviez pas envie de taper ces 2 caractères entre guillemets.

Maintenant, pour que ce bogue [ $# -eq 2 ] Devienne une vulnérabilité, il faudrait que la valeur de $IFS Devienne sous contrôle de - l'attaquant. En théorie, cela ne se produirait normalement que si l'attaquant a réussi à exploiter un autre bogue.

Ce n'est pas inconnu cependant. Un cas courant est lorsque les gens oublient de nettoyer les données avant de les utiliser dans l'expression arithmétique. Nous avons déjà vu ci-dessus qu'il peut permettre l'exécution de code arbitraire dans certains shells, mais dans tous, il permet à l'attaquant de donner à n'importe quelle variable une valeur entière.

Par exemple:

n=$(($1 + 1))
if [ $# -gt 2 ]; then
  echo >&2 "Too many arguments"
  exit 1
fi

Et avec un $1 Avec la valeur (IFS=-1234567890), Cette évaluation arithmétique a l'effet secondaire des paramètres IFS et la prochaine commande [ Échoue, ce qui signifie que la vérification de trop args est contourné.

Qu'en est-il lorsque l'opérateur split + glob n'est pas appelé?

Il y a un autre cas où des guillemets sont nécessaires autour des variables et autres extensions: quand il est utilisé comme modèle.

[[ $a = $b ]]   # a `ksh` construct also supported by `bash`
case $a in ($b) ...; esac

ne testez pas si $a et $b sont identiques (sauf avec zsh) mais si $a correspond au modèle dans $b. Et vous devez citer $b Si vous souhaitez comparer en tant que chaînes (même chose dans "${a#$b}" Ou "${a%$b}" Ou "${a##*$b*}"$b Devrait être cité s'il ne doit pas être pris comme modèle).

Cela signifie que [[ $a = $b ]] Peut retourner vrai dans les cas où $a Est différent de $b (Par exemple lorsque $a Est anything et $b Est *) Ou peut retourner faux lorsqu'ils sont identiques (par exemple lorsque $a Et $b Sont [a]).

Cela peut-il créer une vulnérabilité de sécurité? Oui, comme tout bug. Ici, l'attaquant peut modifier le flux de code logique de votre script et/ou casser les hypothèses que votre script fait. Par exemple, avec un code comme:

if [[ $1 = $2 ]]; then
   echo >&2 '$1 and $2 cannot be the same or damage will incur'
   exit 1
fi

L'attaquant peut contourner la vérification en passant '[a]' '[a]'.

Maintenant, si ni cette correspondance de modèle ni l'opérateur split + glob ne s'appliquent, quel est le danger de laisser une variable sans guillemets?

Je dois admettre que j'écris:

a=$b
case $a in...

Là, la citation ne fait pas de mal mais n'est pas strictement nécessaire.

Cependant, l'omission de guillemets dans ces cas (par exemple dans les réponses aux questions/réponses) a pour effet secondaire d'envoyer un mauvais message aux débutants:  qu'il peut être bien de ne pas citer de variables.

Par exemple, ils peuvent commencer à penser que si a=$b Est OK, alors export a=$b serait aussi bien (qui ce n'est pas dans de nombreux shells comme c'est dans les arguments de la commande export donc dans le contexte de la liste) ou env a=$b.

Qu'en est-il de zsh?

zsh a corrigé la plupart de ces maladresses de conception. Dans zsh (au moins lorsqu'il n'est pas en mode d'émulation sh/ksh), si vous voulez splitting, ou globbing, ou pattern matching, vous devez le demander explicitement: $=var pour diviser et $~var pour glob ou pour que le contenu de la variable soit traité comme un modèle.

Cependant, le fractionnement (mais pas la globalisation) se fait toujours implicitement lors de la substitution de commande sans guillemets (comme dans echo $(cmd)).

En outre, un effet secondaire parfois indésirable de ne pas citer de variable est la suppression de la vidange. Le comportement zsh est similaire à ce que vous pouvez réaliser dans d'autres shells en désactivant complètement le globbing (avec set -f) Et le fractionnement (avec IFS=''). Toujours dedans:

cmd $var

Il n'y aura pas split + glob, mais si $var Est vide, au lieu de recevoir un argument vide, cmd ne recevra aucun argument du tout.

Cela peut provoquer des bugs (comme l'évident [ -n $var ]). Cela peut éventuellement briser les attentes et les hypothèses d'un script et provoquer des vulnérabilités, mais je ne peux pas trouver un exemple pas trop farfelu pour l'instant).

Et quand vous faites avez besoin de l'opérateur split + glob?

Oui, c'est généralement lorsque vous souhaitez laisser votre variable sans guillemets. Mais alors vous devez vous assurer que vous réglez correctement vos opérateurs split et glob avant de l'utiliser. Si vous souhaitez uniquement la partie split et non la partie glob (ce qui est le cas la plupart du temps), vous devez désactiver la globalisation (set -o noglob/set -f) Et corrigez $IFS. Sinon, vous causerez également des vulnérabilités (comme l'exemple CGI de David Korn mentionné ci-dessus).

Conclusion

En bref, laisser une variable (ou substitution de commande ou expansion arithmétique) sans guillemets peut être très dangereux, en particulier lorsqu'elle est effectuée dans les mauvais contextes, et il est très difficile de savoir quels sont ces mauvais contextes.

C'est l'une des raisons pour lesquelles elle est considérée mauvaise pratique.

Merci d'avoir lu jusqu'ici. Si cela vous dépasse, ne vous inquiétez pas. On ne peut pas s'attendre à ce que tout le monde comprenne toutes les implications de l'écriture de son code comme il l'écrit. C'est pourquoi nous avons des recommandations de bonnes pratiques, donc elles peuvent être suivies sans nécessairement comprendre pourquoi.

(et si ce n'est pas encore évident, veuillez éviter d'écrire du code sensible à la sécurité dans des shells).

Et veuillez citer vos variables sur vos réponses sur ce site!


¹Dans ksh93 Et pdksh et dérivés, expansion de l'accolade est également effectué sauf si la globalisation est désactivée (dans le cas des versions de ksh93 Jusqu'à ksh93u + , même lorsque l'option braceexpand est désactivée).

207
Stéphane Chazelas

[Inspiré par cette réponse par cas .]

Mais si …?

Mais que se passe-t-il si mon script définit une variable sur une valeur connue avant de l'utiliser? En particulier, que se passe-t-il si elle définit une variable sur l'une ou plusieurs des valeurs possibles (mais qu'elle la définit toujours sur quelque chose de connu) et qu'aucune des valeurs ne contient d'espace ou de caractères globaux ? N'est-il pas sûr de l'utiliser sans guillemets dans ce cas ?

Et que se passe-t-il si l'une des valeurs possibles est la chaîne vide et que je dépend de la "suppression des vides"? C'est-à-dire, si la variable contient la chaîne vide, je ne veux pas obtenir la chaîne vide dans ma commande; Je ne veux rien. Par exemple,

si une_condition
 puis 
 ignorecase = "- i" 
 else 
 ignorecase = "" 
 fi 
 # Notez que les guillemets dans les commandes ci-dessus sont ne pas strictement nécessaire.
 grep $ ignorecase autre_grep_args

Je ne peux pas dire grep "$ignorecase" other_grep_args; cela échouera si $ignorecase est la chaîne vide.

Réponse:

Comme indiqué dans l'autre réponse, cela échouera toujours si IFS contient un - Ou un i. Si vous vous êtes assuré que IFS ne contient aucun caractère dans votre variable (et vous êtes sûr que votre variable ne contient aucun caractère global), cela est probablement sûr.

Mais il existe un moyen plus sûr (bien que ce soit quelque peu moche et assez peu intuitif): utilisez ${ignorecase:+"$ignorecase"}. De la spécification POSIX Shell Command Language , sous 2.6.2 Expansion des paramètres ,

${parameter:+[Word]}

    Utilisez une valeur alternative. Si parameter est unset ou null, null sera remplacé ; sinon, l'expansion de Word (ou une chaîne vide si Word est omis) doit être remplacé.

L'astuce ici, telle qu'elle est, est que nous utilisons ignorecase comme parameter et "$ignorecase" Comme Word. Donc ${ignorecase:+"$ignorecase"} Signifie

Si $ignorecase Est non défini ou nul (c.-à-d. Vide), null (c.-à-d., Sans guillemets rien ) ne sera substitué; sinon, l'extension de "$ignorecase" sera remplacée.

Cela nous amène là où nous voulons aller: si la variable est définie sur la chaîne vide, elle sera "supprimée" (cette expression alambiquée entière sera évaluée à rien - même pas une chaîne vide), et si la variable a une valeur non vide, nous obtenons cette valeur, entre guillemets.


Mais si …?

Mais que faire si j'ai une variable que je veux/dois être divisée en mots? (C'est autrement comme le premier cas; mon script a défini la variable, et je suis sûr qu'elle ne contient aucun caractère global. Mais elle peut contenir des espaces, et je veux qu'elle soit divisée en arguments séparés dans l'espace limites.
P.S. Je veux toujours retirer les vides.)

Par exemple,

si une_condition
 alors 
 critère = "- type f" 
 sinon 
 critère = "" 
 fi 
 si some_other_condition
 puis 
 critère = "$ critère -meure +42" 
 fi 
 trouver "$ start_directory" $ critère autre_trouver_args

Réponse:

Vous pourriez penser que c'est un cas pour utiliser eval. Non! Résistez à la tentation de même penser à utiliser eval ici.

Encore une fois, si vous vous êtes assuré que IFS ne contient aucun caractère dans votre variable (à l'exception des espaces que vous souhaitez respecter) et que vous êtes sûr que votre variable ne contient aucun caractère global , alors ce qui précède est probablement sûr.

Mais, si vous utilisez bash (ou ksh, zsh ou yash), il existe un moyen plus sûr: utilisez un tableau:

si une_condition
 puis 
 critère = (- type f) # Vous pourriez dire `critère = (" - type "" f ")`, mais c'est vraiment inutile. 
 sinon 
 critères = () # Ne pas utilisez des guillemets sur cette commande! 
 fi 
 si some_other_condition
 puis 
 critères + = (- mtime +42) # Remarque: pas `=`, mais `+= `, pour ajouter (ajouter) à un tableau. 
 fi 
 trouver" $ start_directory "" $ {critères [@]} " autre_trouver_args

De bash (1) ,

Tout élément d'un tableau peut être référencé à l'aide de ${name[subscript]}. … Si subscript est @ ou *, le mot s'étend à tous les membres de name. Ces indices ne diffèrent que lorsque le mot apparaît entre guillemets doubles. Si le mot est entre guillemets,… ${name[@]} Développe chaque élément de name en un mot distinct.

Ainsi "${criteria[@]}" Se développe (dans l'exemple ci-dessus) le zéro, deux ou quatre éléments du tableau criteria, chacun cité. En particulier, si aucune des conditions s n'est vraie, le tableau criteria n'a pas de contenu (tel que défini par l'instruction criteria=()), et "${criteria[@]}" est évalué à rien (pas même une chaîne vide gênante).


Cela devient particulièrement intéressant et compliqué lorsque vous avez affaire à plusieurs mots, dont certains sont des entrées dynamiques (utilisateur), que vous ne connaissez pas à l'avance, et peuvent contenir des espaces ou d'autres caractères spéciaux. Considérer:

printf "Entrez le nom du fichier à rechercher:" 
 lire fname 
 si ["$ fname"! = ""] 
 puis 
 critère + = (- nom " $ fname ") 
 fi

Notez que $fname Est cité chaque fois qu'il est utilisé. Cela fonctionne même si l'utilisateur entre quelque chose comme foo bar Ou foo*; "${criteria[@]}" Est évalué à -name "foo bar" Ou -name "foo*". (N'oubliez pas que chaque élément du tableau est cité.)

Les baies ne fonctionnent pas dans tous les shells POSIX; les tableaux sont un ksh/bash/zsh/yash-ism. Sauf… il y a un tableau que tous les shells supportent: la liste des arguments, a.k.a. "$@". Si vous avez terminé avec la liste d'arguments avec laquelle vous avez été invoqué (par exemple, vous avez copié tous les "paramètres de position" (arguments) dans des variables, ou les avez autrement traités), vous pouvez utiliser la liste d'arguments comme un tableau:

si une_condition
 puis 
 set - -type f # Vous pourriez dire `set -" -type "" f "`, mais c'est vraiment inutile. 
 sinon 
 régler - 
 fi 
 si some_other_condition
 puis 
 set - "$ @" -mtime +42 
 fi 
 # De même: set - "$ @" -name "$ fname" 
 trouver "$ start_directory" "$ @" autre_trouver_args

La construction "$@" (Qui, historiquement, venait en premier) a la même sémantique que "${name[@]}" - elle étend chaque argument (c'est-à-dire chaque élément de la liste d'arguments) en un mot distinct, comme si vous avait tapé "$1" "$2" "$3" ….

Extrait de la spécification POSIX Shell Command Language , sous 2.5.2 Paramètres spéciaux ,

@

    S'étend aux paramètres positionnels, en commençant par un, produisant initialement un champ pour chaque paramètre positionnel défini. …, Les champs initiaux sont conservés en tant que champs séparés,…. S'il n'y a pas de paramètres de position, l'expansion de @ ne générera aucun champ, même lorsque @ est entre guillemets doubles; …

Le texte intégral est quelque peu cryptique; le point clé est qu'il spécifie que "$@" doit générer zéro champs lorsqu'il n'y a pas de paramètres de position. Note historique: lorsque "$@" A été introduit pour la première fois dans le Bourne Shell (prédécesseur de bash) en 1979, il y avait un bogue selon lequel "$@" Était remplacé par une seule chaîne vide lorsqu'il n'y avait pas de paramètres de position; voir Que signifie ${1+"$@"} dans un script Shell et en quoi diffère-t-il de "$@"? , The Traditional Bourne Shell Family , - Que signifie ${1+"$@"} Signifie ... et où est-ce nécessaire? , et "$@" Contre ${1+"$@"} .


Les tableaux aident également à la première situation:

si une_condition
 puis 
 ignorecase = (- i) # Vous pourriez dire `ignorecase = (" - i ")`, mais c'est vraiment inutile. 
 sinon 
 ignorecase = () # Ne pas utilisez des guillemets sur cette commande! 
 fi 
 grep "$ {ignorecase [@]}" autre_grep_args

____________________

P.S. (csh)

Cela va sans dire, mais, pour le bénéfice des gens qui sont nouveaux ici: csh, tcsh, etc., ne sont pas des shells Bourne/POSIX. C’est une toute autre famille. Un cheval d'une couleur différente. Un tout autre jeu de balle. Une race de chat différente. Oiseaux d'une autre plume. Et, plus particulièrement, une autre boîte de vers.

Une partie de ce qui a été dit sur cette page s'applique à csh; comme: c'est une bonne idée de citer toutes vos variables, sauf si vous avez une bonne raison de ne pas le faire, et vous êtes sûr de savoir ce que vous faites. Mais, dans csh, chaque variable est un tableau - il se trouve que presque chaque variable est un tableau d'un seul élément, et agit assez similaire à une variable Shell ordinaire dans les shells Bourne/POSIX. Et la syntaxe est terriblement différente (et je veux dire terriblement ). Nous ne dirons donc rien de plus sur les shells de la famille csh ici.

J'étais sceptique quant à la réponse de Stéphane, mais il est possible d'abuser $#:

$ set `seq 101`

$ IFS=0

$ echo $#
1 1

ou $?:

$ IFS=0

$ awk 'BEGIN {exit 101}'

$ echo $?
1 1

Ce sont des exemples artificiels, mais le potentiel existe.

11
Steven Penny