web-dev-qa-db-fra.com

Quand faut-il citer deux fois?

L'ancien conseil était de double-citer toute expression impliquant un $VARIABLE, au moins si l'on voulait qu'il soit interprété par le Shell comme un seul élément, sinon, tous les espaces dans le contenu de $VARIABLE jetterait le Shell.

Je comprends, cependant, que dans les versions plus récentes des shells, les guillemets doubles ne sont plus toujours nécessaires (au moins aux fins décrites ci-dessus). Par exemple, dans bash:

% FOO='bar baz'
% [ $FOO = 'bar baz' ] && echo OK
bash: [: too many arguments
% [[ $FOO = 'bar baz' ]] && echo OK
OK
% touch 'bar baz'
% ls $FOO
ls: cannot access bar: No such file or directory
ls: cannot access baz: No such file or directory

Dans zsh, en revanche, les trois mêmes commandes réussissent. Par conséquent, sur la base de cette expérience, il semble que, dans bash, on puisse omettre les guillemets doubles à l'intérieur de [[ ... ]], mais pas à l'intérieur [ ... ] ni dans les arguments de ligne de commande, alors que dans zsh, les guillemets doubles peuvent être omis dans tous ces cas.

Mais inférer des règles générales à partir d'exemples anecdotiques comme celui-ci est une proposition hasardeuse. Ce serait bien de voir un résumé des cas où des guillemets doubles sont nécessaires. Je suis principalement intéressé par zsh, bash et /bin/sh.

127
kjo

Tout d'abord, séparez zsh du reste. Ce n'est pas une question de coquilles anciennes vs modernes: zsh se comporte différemment. Les concepteurs de zsh ont décidé de le rendre incompatible avec les shells traditionnels (Bourne, ksh, bash), mais plus simple d'utilisation.

Deuxièmement, il est beaucoup plus facile d'utiliser des guillemets doubles tout le temps que de se rappeler quand ils sont nécessaires. Ils sont nécessaires la plupart du temps, vous devrez donc apprendre quand ils ne sont pas nécessaires, pas quand ils sont nécessaires.

En un mot, les guillemets doubles sont nécessaires partout où une liste de mots ou un modèle est attendu . Ils sont facultatifs dans les contextes où une chaîne brute est attendue par l'analyseur.

Que se passe-t-il sans guillemets

Notez que sans guillemets doubles, deux choses se produisent.

  1. Tout d'abord, le résultat de l'expansion (la valeur de la variable pour une substitution de paramètre comme ${foo}, Ou la sortie de la commande pour une substitution de commande comme $(foo)) est divisé en mots partout où il contient un espace.
    Plus précisément, le résultat de l'expansion est divisé à chaque caractère qui apparaît dans la valeur de la variable IFS (caractère séparateur). Si une séquence de caractères de séparation contient des espaces (espace, tabulation ou nouvelle ligne), les espaces sont comptés comme un seul caractère; les séparateurs non blancs en début, en fin ou répétés conduisent à des champs vides. Par exemple, avec IFS=" :", :one::two : three: :four  Produit des champs vides avant one, entre one et two, et (un seul) entre three et four.
  2. Chaque champ résultant de la division est interprété comme un glob (un motif générique) s'il contient l'un des caractères \[*?. Si ce modèle correspond à un ou plusieurs noms de fichiers, le modèle est remplacé par la liste des noms de fichiers correspondants.

Une expansion de variable sans guillemets $foo Est familièrement connue comme "l'opérateur split + glob", contrairement à "$foo" Qui prend juste la valeur de la variable foo. Il en va de même pour la substitution de commandes: "$(foo)" est une substitution de commandes, $(foo) est une substitution de commandes suivie de split + glob.

Où vous pouvez omettre les guillemets doubles

Voici tous les cas auxquels je peux penser dans un shell de style Bourne où vous pouvez écrire une substitution de variable ou de commande sans guillemets doubles, et la valeur est interprétée littéralement.

  • Sur le côté droit d'une affectation.

    var=$stuff
    a_single_star=*
    

    Notez que vous avez besoin des guillemets doubles après export, car il s'agit d'un code interne ordinaire, pas d'un mot clé. Cela n'est vrai que dans certains shells tels que dash, zsh (en émulation sh), yash ou chic; bash et ksh traitent tous les deux export spécialement.

    export VAR="$stuff"
    
  • Dans une instruction case.

    case $var in …
    

    Notez que vous avez besoin de guillemets doubles dans un modèle de cas. Le fractionnement de mots ne se produit pas dans un modèle de casse, mais une variable sans guillemets est interprétée comme un modèle tandis qu'une variable entre guillemets est interprétée comme une chaîne littérale.

    a_star='a*'
    case $var in
      "$a_star") echo "'$var' is the two characters a, *";;
       $a_star) echo "'$var' begins with a";;
    esac
    
  • Entre parenthèses doubles. Les crochets doubles sont une syntaxe spéciale Shell.

    [[ -e $filename ]]
    

    Sauf que vous avez besoin de guillemets doubles où un modèle ou une expression régulière est attendu: sur le côté droit de = Ou == Ou != Ou =~ .

    a_star='a*'
    if [[ $var == "$a_star" ]]; then echo "'$var' is the two characters a, *"
    Elif [[ $var == $a_star ]]; then echo "'$var' begins with a"
    fi
    

    Vous avez besoin de guillemets doubles comme d'habitude entre crochets simples [ … ] Car ils sont une syntaxe Shell ordinaire (c'est une commande qui se trouve être appelée [). Voir crochets simples ou doubles

  • Dans une redirection dans des shells POSIX non interactifs (pas bash, ni ksh88).

    echo "hello world" >$filename
    

    Certains shells, lorsqu'ils sont interactifs, traitent la valeur de la variable comme un motif générique. POSIX interdit ce comportement dans les shells non interactifs, mais quelques shells, y compris bash (sauf en mode POSIX) et ksh88 (y compris lorsqu'ils sont trouvés comme (soi-disant) POSIX sh de certains Unices commerciaux comme Solaris) le font toujours là (bash essaie aussi splitting et la redirection échoue sauf si split + globbing donne exactement un mot), c'est pourquoi c'est mieux citer les cibles des redirections dans un script sh au cas où vous voudriez le convertir en script bash un jour, ou l'exécuter sur un système où sh n'est pas conforme sur ce point, ou il peut être sourced à partir de shells interactifs.

  • À l'intérieur d'une expression arithmétique. En fait, vous devez laisser les guillemets pour qu'une variable soit analysée en tant qu'expression arithmétique.

    expr=2*2
    echo "$(($expr))"
    

    Cependant, vous avez besoin des guillemets autour de l'expansion arithmétique car ils sont sujets au fractionnement de Word dans la plupart des shells comme POSIX l'exige (!?).

  • Dans un indice de tableau associatif.

    typeset -A a
    i='foo bar*qux'
    a[foo\ bar\*qux]=hello
    echo "${a[$i]}"
    

Une variable sans guillemets et une substitution de commande peuvent être utiles dans de rares circonstances:

  • Lorsque la valeur de la variable ou la sortie de la commande se compose d'une liste de modèles globaux et que vous souhaitez développer ces modèles dans la liste des fichiers correspondants.
  • Lorsque vous savez que la valeur ne contient aucun caractère générique, ce $IFS N'a pas été modifié et vous souhaitez le diviser en espaces.
  • Lorsque vous souhaitez fractionner une valeur à un certain caractère: désactivez la globalisation avec set -f, Définissez IFS sur le caractère séparateur (ou laissez-le tranquille pour fractionner à l'espace), puis faites l'expansion.

Zsh

Dans zsh, vous pouvez omettre la plupart du temps les guillemets doubles, à quelques exceptions près.

  • $var Ne se développe jamais sur plusieurs mots, mais il se développe sur la liste vide (par opposition à une liste contenant un seul mot vide) si la valeur de var est la chaîne vide. Contraste:

    var=
    print -l $var foo        # prints just foo
    print -l "$var" foo      # prints an empty line, then foo
    

    De même, "${array[@]}" S'étend à tous les éléments du tableau, tandis que $array Ne s'étend qu'aux éléments non vides.

  • L'indicateur d'expansion du paramètre @ Nécessite parfois des guillemets doubles autour de la substitution entière: "${(@)foo}".

  • La substitution de commande subit une division de champ si elle n'est pas citée: echo $(echo 'a'; echo '*') affiche a * (Avec un seul espace) tandis que echo "$(echo 'a'; echo '*')" imprime la chaîne non modifiée de deux lignes. Utilisez "$(somecommand)" pour obtenir la sortie de la commande dans un seul mot, sans retour à la ligne final. Utilisez "${$(somecommand; echo _)%?}" pour obtenir la sortie exacte de la commande, y compris les retours à la ligne finaux. Utilisez "${(@f)$(somecommand)}" pour obtenir un tableau de lignes à partir de la sortie de la commande.