Variable avec guillemets «$ ()»

12

J'ai écrit ce script:

#!/bin/bash
while [ true ] 
do
    currentoutput="$(lsusb)"
    if [ "$currentoutput" != "$lastoutput" ]
    then
        echo "" date and Time >> test.log
        date +%x_r >> test.log
        lastoutput="$(lsusb)"
        lsusb >> test.log
    fi
    sleep 5
done

Je suis un débutant qui essaie d'apprendre rapidement et j'ai une question sur les guillemets de la variable .

Mettez une variable entre $ (), je comprends, mais pourquoi les guillemets sont-ils nécessaires même dans l' ifinstruction? Est-ce pour faire une commande imbriquée?

Shankhara
la source
5
Notez que while [ true ]cela produit une boucle infinie, mais peut-être pas pour la raison pour laquelle vous pensez que c'est le cas; produit while [ false ] également une boucle infinie, car avec un seul argument, [ ... ]réussit si cet argument est une chaîne non vide. exécutera enwhile true fait une commande nommée (qui réussit toujours). true
chepner
4
Vous ne mettez pas de variable à l'intérieur $(). Vous mettez une commande à l' intérieur $().
Wildcard

Réponses:

22

Les guillemets empêchent la "division des mots". C'est-à-dire: décomposer les variables en plusieurs éléments au niveau des espaces (ou pour être plus exact, aux espaces, tabulations et sauts de ligne tels que définis dans la valeur de la $IFSvariable shell par défaut ).

Par exemple,

$ var="one two"
$ howmany(){ echo $#;  }
$ howmany $var
2
$ howmany "$var"
1

Ici, nous définissons la howmanyfonction qui nous permet juste de savoir combien de paramètres de position sont donnés. Comme vous pouvez le voir, deux éléments sont passés à la variable et, avec les guillemets, le texte de la variable est traité comme une seule unité.

Ceci est important pour une transmission précise des informations. Par exemple, si la variable contient le chemin d'accès au fichier et que le nom de fichier contient des espaces n'importe où dans le chemin d'accès, la commande que vous essayez d'exécuter peut échouer ou donner des résultats inexacts. Si nous essayions de créer un fichier avec la $varvariable, touch $varcréerait deux fichiers, mais touch "$var"un seul.

Il en va de même pour votre [ "$currentoutput" != "$lastoutput" ]part. Ce test particulier effectue une comparaison sur deux chaînes. Lorsque le test s'exécute, la [commande doit voir 3 arguments - une chaîne de texte, l' !=opérateur et une autre chaîne de texte. Garder les guillemets doubles empêche le fractionnement des mots et la [commande voit exactement ces 3 arguments. Maintenant, que se passe-t-il si les variables ne sont pas citées?

$ var="hello world"
$ foo="hi world"
$ [ $var != $foo ]
bash: [: too many arguments
$ 

Ici, le fractionnement de mot se produit et [voit à la place deux chaînes helloet worldsuivi de !=, suivi de deux autres chaînes hi world. Le point clé est que, sans guillemets doubles, le contenu des variables est compris comme des unités distinctes plutôt que comme un élément entier.

L'attribution d'une substitution de commande ne nécessite pas de guillemets doubles comme dans

var=$( df )

où vous avez dfenregistré la sortie de la commande var. Cependant, c'est une bonne habitude de toujours doubler les variables entre guillemets et la substitution de commandes, $(...)sauf si vous voulez en fait que la sortie soit traitée comme des éléments distincts.


En passant, le

while [ true ]

une partie peut être

while true

[est une commande qui évalue ses arguments et [ whatever ]est toujours vraie indépendamment de ce qu'il y a à l'intérieur. En revanche, while trueutilise la commande truequi renvoie toujours le statut de sortie de réussite (et c'est exactement ce dont la whileboucle a besoin). La différence est un peu plus de clarté et moins de tests effectués. Alternativement, vous pouvez également utiliser :au lieu detrue

Les doubles guillemets echo "" date and Timepourraient en partie être supprimés. Ils insèrent simplement une chaîne vide et ajoutent un espace supplémentaire à la sortie. Si vous le souhaitez, n'hésitez pas à les conserver, mais il n'y a pas de valeur fonctionnelle particulière dans ce cas.

lsusb >> test.log

Cette partie pourrait probablement être remplacée par echo "$currentoutput" >> test.log. Il n'y a aucune raison de lsusbrecommencer après qu'il ait déjà été exécuté currentoutput=$(lsusb). Dans les cas où les retours à la ligne doivent être conservés dans la sortie - on peut voir la valeur de l'exécution d'une commande plusieurs fois, mais dans le cas contraire, lsusbcela n'est pas nécessaire. Moins vous appelez de commandes externes, mieux c'est, car chaque appel à une commande non intégrée entraîne des coûts de CPU, d'utilisation de la mémoire et de temps d'exécution (même si les commandes sont probablement préchargées depuis la mémoire).


Voir également:

Sergiy Kolodyazhnyy
la source
1
Excellente réponse, si vous ne l'avez pas déjà fait, vous devriez écrire un livre sur bash. Mais dans la dernière partie, ça ne devrait pas echo "$currentoutput" >> test.log être mieux printf '%s\n' "$currentoutput" >> test.log ?
pLumo
2
@RoVo Oui, printfdevrait être préféré pour la portabilité. Puisque nous utilisons ici un script spécifique à bash, il peut être excusé pour l'utiliser echo. Mais votre observation est tout à fait correcte
Sergiy Kolodyazhnyy
1
@SergiyKolodyazhnyy, ... Je ne suis pas sûr que ce echosoit complètement fiable même lorsque le shell est connu pour être bash (et que la valeur des drapeaux xpg_echoet posixest connue); si votre valeur peut être -nou -e, elle peut disparaître au lieu d'être imprimée.
Charles Duffy
4

Dans currentoutput="$(lsusb)"lsusb n'est pas une variable, c'est une commande. Ce que fait cette instruction, elle exécute la lsusbcommande et affecte sa sortie à la currentoutputvariable.

Une syntaxe plus ancienne pour cela était

currentoutput=`lsusb`

vous pouvez le trouver dans de nombreux exemples et scripts

Pour répondre à l'autre partie de votre question, if [ ]voici comment la syntaxe de ifest définie dans bash. Voir plus dans https://www.tldp.org/LDP/Bash-Beginners-Guide/html/sect_07_01.html

marosg
la source
3
Je pense qu'il est important de dire que [ ]c'est en fait la testcommande. Vous pouvez également utiliser des ifinstructions avec d'autres commandes, car elles reposent sur un test 0 ou non nul de leur code de sortie.
Arronical
... en effet, il vaut beaucoup mieux exécuter if grep -qe "somestring" fileque exécuter grep -qe "somestring" file; if [ $? = 0 ]; then ..., donc l'affirmation qui if [ ...fait partie de la définition de la ifsyntaxe n'est pas seulement trompeuse mais conduit à de mauvaises pratiques.
Charles Duffy
4

Ce qui suit exécute la commande externe commandet renvoie sa sortie.

"$(command)"

Sans les crochets / parenthèses, cela rechercherait une variable au lieu d'exécuter une commande:

"$variable"

Quant à la différence entre $variableet "$variable", elle devient pertinente lorsque $variablecontient des espaces. Lors de l'utilisation "$variable", le contenu entier de la variable sera inséré dans une seule chaîne même si le contenu comprend des espaces. Lors de l'utilisation $variabledu contenu de la variable peut être développé dans une liste d'arguments de plusieurs arguments.

thomasrutter
la source
HI @thomasrutter, je suis désolé, je voulais dire guillemet ... Je modifie mon commentaire maintenant!
Shankhara
0

Pour être à contre-courant - bash vous recommande d'utiliser la construction [[ ... ]]over [ ... ]pour éviter d'avoir à citer des variables dans le test et donc les problèmes associés de séparation de mots que les autres ont signalés.

[est fourni en bash pour la compatibilité POSIX avec les scripts destinés à être exécutés sous #!/bin/shou ceux qui sont portés sur bash - pour la plupart, vous devriez l'éviter en faveur de [[.

par exemple

# With [ - quotes are needed

$ foo='one two'; bar='one two'; [ $foo = $bar ] && echo "they're equal"
-bash: [: too many arguments

$ foo='one two'; bar='one two'; [ "$foo" = "$bar" ] && echo "they're equal"                                                                                                              
they're equal

# versus [[ - quotes not needed

$ foo='one two'; bar='one two'; [[ $foo = $bar ]] && echo "they're equal"
they're equal

shalomb
la source
bashne fait pas une telle recommandation; il fournit [[ ... ]] ce qui peut être plus pratique et possède certaines fonctionnalités qui [ ... ]ne le font pas, mais il n'y a aucun problème à l'utiliser [ ... ] correctement .
chepner
@chepner - Je devrais peut-être être plus clair ici. Bien sûr, ce n'est pas une recommandation explicite dans la page de manuel de bash mais étant donné [[que tout le [fait et plus encore, les nombreuses fois [déclenchent les gens et essaient ensuite de moderniser les fonctionnalités de [[back to [seulement pour échouer et laisser des bugs dispersés - il est juste de dire que c'est un recommandation de la communauté dans la mesure où les guides de style bash les plus pertinents conseillent de ne jamais les utiliser[ .
shalomb