Portée de la variable Bash

104

Veuillez m'expliquer pourquoi la toute dernière echodéclaration est vide? Je m'attends à ce que cela XCODEsoit incrémenté dans la boucle while à une valeur de 1:

#!/bin/bash
OUTPUT="name1 ip ip status" # normally output of another command with multi line output

if [ -z "$OUTPUT" ]
then
        echo "Status WARN: No messages from SMcli"
        exit $STATE_WARNING
else
        echo "$OUTPUT"|while read NAME IP1 IP2 STATUS
        do
                if [ "$STATUS" != "Optimal" ]
                then
                        echo "CRIT: $NAME - $STATUS"
                        echo $((++XCODE))
                else
                        echo "OK: $NAME - $STATUS"
                fi
        done
fi

echo $XCODE

J'ai essayé d'utiliser la déclaration suivante au lieu de la ++XCODEméthode

XCODE=`expr $XCODE + 1`

et il ne s'imprimera pas non plus en dehors de l'instruction while. Je pense qu'il me manque quelque chose sur la portée variable ici, mais la vieille page de manuel ne me le montre pas.

Matt P
la source
Où initialisez-vous XCODE à quelque chose qui peut être incrémenté?
Paul Tomblin
J'ai essayé de lancer un "XCODE = 0" en haut du code, en dehors de l'instruction while
Matt P
Sans la cruauté, cela fonctionne pour moi. #! / bin / bash pour i dans 1 2 3 4 5; do echo $ ((++ XCODE)) done echo "fin:" $ XCODE Je pense que votre problème n'a rien à voir avec la portée des variables et tout à voir avec ce qui se passe dans le moment.
Paul Tomblin
D'accord ... il semble que cela ait à voir avec la boucle "while read"?
Matt P
Il y a une FAQ Bash à ce sujet: mywiki.wooledge.org/BashFAQ/024
Fabio dit Réintégrer Monica le

Réponses:

117

Comme vous effectuez un piping dans la boucle while, un sous-shell est créé pour exécuter la boucle while.

Maintenant, ce processus enfant a sa propre copie de l'environnement et ne peut pas renvoyer de variables à son parent (comme dans n'importe quel processus unix).

Par conséquent, vous devrez vous restructurer pour ne pas vous lancer dans la boucle. Vous pouvez également exécuter une fonction, par exemple, et echola valeur que vous souhaitez renvoyer par le sous-processus.

http://tldp.org/LDP/abs/html/subshells.html#SUBSHELL

pixelbeat
la source
2
cela ne faisait que répondre à tant de problèmes apparemment aléatoires que je rencontrais avec les scripts bash.
Daniel Agans
Cette réponse parfaite me bouleverse tellement et explique un comportement vraiment étrange dans notre système CI.
KayCee
108

Le problème est que les processus assemblés avec un tube sont exécutés dans des sous-shell (et ont donc leur propre environnement). Tout ce qui se passe dans le whilen'affecte rien à l'extérieur du tuyau.

Votre exemple spécifique peut être résolu en réécrivant le tube en

while ... do ... done <<< "$OUTPUT"

ou peut-être

while ... do ... done < <(echo "$OUTPUT")
mweerden
la source
32
Pour ceux qui sont confus sur ce qu'est la syntaxe <() entière (comme je l'étais), cela s'appelle "Substitution de processus", et l'utilisation spécifique détaillée ci-dessus peut être vue ici: mywiki.wooledge.org/ProcessSubstitution
Ross Aiken
2
La substitution de processus est quelque chose que tout le monde devrait utiliser régulièrement! C'est super utile. Je fais quelque chose comme vimdiff <(grep WARN log.1 | sort | uniq) <(grep WARN log.2 | sort | uniq)tous les jours. Considérez que vous pouvez en utiliser plusieurs à la fois et les traiter comme des fichiers ... POSSIBILITÉS!
Bruno Bronosky
8

Cela devrait également fonctionner (car echo et while sont dans le même sous-shell):

#!/bin/bash
cat /tmp/randomFile | (while read line
do
    LINE="$LINE $line"
done && echo $LINE )
sano
la source
3

Une autre option:

#!/bin/bash
cat /some/file | while read line
do
  var="abc"
  echo $var | xsel -i -p  # redirect stdin to the X primary selection
done
var=$(xsel -o -p)  # redirect back to stdout
echo $var

EDIT: Ici, xsel est une exigence (installez-le). Vous pouvez également utiliser xclip: xclip -i -selection clipboard au lieu de xsel -i -p

Rammix
la source
J'obtiens une erreur: ./scraper.sh: ligne 111: xsel: commande non trouvée ./scraper.sh: ligne 114: xsel: commande non trouvée
3kstc
@ 3kstc évidemment, installez xsel. Vous pouvez également utiliser xclip, mais son utilisation est un peu différente. Point principal ici: premièrement, vous mettez la sortie dans un presse-papiers (3 d'entre eux sous Linux), deuxième - vous la récupérez à partir de là et l'envoyez vers stdout.
Rammix
1
 #!/bin/bash
 OUTPUT="name1 ip ip status"
+export XCODE=0;
 if [ -z "$OUTPUT" ]
----

                     echo "CRIT: $NAME - $STATUS"
-                    echo $((++XCODE))
+                    export XCODE=$(( $XCODE + 1 ))
             else

echo $XCODE

voir si ces changements aident

Kent Fredric
la source
En faisant cela, j'obtiens maintenant un "0" à imprimer pour la dernière instruction d'écho. cependant, je m'attends à ce que la valeur soit 1 et non nulle. Aussi, pourquoi l'utilisation de l'exportation? Je suppose que cela le force dans l'environnement?
Matt P
0

Une autre option consiste à afficher les résultats dans un fichier à partir du sous-shell, puis à le lire dans le shell parent. quelque chose comme

#!/bin/bash
EXPORTFILE=/tmp/exportfile${RANDOM}
cat /tmp/randomFile | while read line
do
    LINE="$LINE $line"
    echo $LINE > $EXPORTFILE
done
LINE=$(cat $EXPORTFILE)
libre penseur
la source
0

J'ai contourné ça quand je faisais mon propre petit du:

ls -l | sed '/total/d ; s/  */\t/g' | cut -f 5 | 
( SUM=0; while read SIZE; do SUM=$(($SUM+$SIZE)); done; echo "$(($SUM/1024/1024/1024))GB" )

Le fait est que je crée un sous-shell avec () contenant ma variable SUM et le while, mais je passe dans l'ensemble () au lieu de dans le while lui-même, ce qui évite le gotcha.

Adrian May
la source