258 votes

Une variable modifiée à l'intérieur d'une boucle while n'est pas mémorisée

Dans le programme suivant, si je place la variable $foo à la valeur 1 à l'intérieur du premier if il fonctionne dans le sens où sa valeur est mémorisée après l'instruction if. Cependant, lorsque je donne à la même variable la valeur 2 dans une instruction if qui se trouve à l'intérieur d'un while il est oublié après l'instruction while boucle. C'est comme si j'utilisais une sorte de copie de la variable $foo à l'intérieur de la while et je ne modifie que cette copie particulière. Voici un programme de test complet :

#!/bin/bash

set -e
set -u 
foo=0
bar="hello"  
if [[ "$bar" == "hello" ]]
then
    foo=1
    echo "Setting \$foo to 1: $foo"
fi

echo "Variable \$foo after if statement: $foo"   
lines="first line\nsecond line\nthird line" 
echo -e $lines | while read line
do
    if [[ "$line" == "second line" ]]
    then
    foo=2
    echo "Variable \$foo updated to $foo inside if inside while loop"
    fi
    echo "Value of \$foo in while loop body: $foo"
done

echo "Variable \$foo after while loop: $foo"

# Output:
# $ ./testbash.sh
# Setting $foo to 1: 1
# Variable $foo after if statement: 1
# Value of $foo in while loop body: 1
# Variable $foo updated to 2 inside if inside while loop
# Value of $foo in while loop body: 2
# Value of $foo in while loop body: 2
# Variable $foo after while loop: 1

# bash --version
# GNU bash, version 4.1.10(4)-release (i686-pc-cygwin)

1voto

Kemin Zhou Points 31

Il s'agit d'une question intéressante qui touche à un concept très fondamental du Bourne shell et du subshell. Je propose ici une solution différente des solutions précédentes en effectuant une sorte de filtrage. Je vais donner un exemple qui peut être utile dans la vie réelle. Il s'agit d'un fragment permettant de vérifier que les fichiers téléchargés sont conformes à une somme de contrôle connue. Le fichier de somme de contrôle ressemble à ce qui suit (montrant seulement 3 lignes) :

49174 36326 dna_align_feature.txt.gz
54757     1 dna.txt.gz
55409  9971 exon_transcript.txt.gz

Le shell script :

#!/bin/sh

.....

failcnt=0 # this variable is only valid in the parent shell
#variable xx captures all the outputs from the while loop
xx=$(cat ${checkfile} | while read -r line; do
    num1=$(echo $line | awk '{print $1}')
    num2=$(echo $line | awk '{print $2}')
    fname=$(echo $line | awk '{print $3}')
    if [ -f "$fname" ]; then
        res=$(sum $fname)
        filegood=$(sum $fname | awk -v na=$num1 -v nb=$num2 -v fn=$fname '{ if (na == $1 && nb == $2) { print "TRUE"; } else { print "FALSE"; }}')
        if [ "$filegood" = "FALSE" ]; then
            failcnt=$(expr $failcnt + 1) # only in subshell
            echo "$fname BAD $failcnt"
        fi
    fi
done | tail -1) # I am only interested in the final result
# you can capture a whole bunch of texts and do further filtering
failcnt=${xx#* BAD } # I am only interested in the number
# this variable is in the parent shell
echo failcnt $failcnt
if [ $failcnt -gt 0 ]; then
    echo $failcnt files failed
else
    echo download successful
fi

Le parent et le sous-shell communiquent par le biais de la commande echo. Vous pouvez choisir un texte facile à analyser pour le shell parent. Cette méthode ne rompt pas avec votre mode de pensée habituel, mais vous devez effectuer un traitement ultérieur. Pour ce faire, vous pouvez utiliser grep, sed, awk, etc.

0voto

Marcin Points 99

Que diriez-vous d'une méthode très simple

    +call your while loop in a function 
     - set your value inside (nonsense, but shows the example)
     - return your value inside 
    +capture your value outside
    +set outside
    +display outside

    #!/bin/bash
    # set -e
    # set -u
    # No idea why you need this, not using here

    foo=0
    bar="hello"

    if [[ "$bar" == "hello" ]]
    then
        foo=1
        echo "Setting  \$foo to $foo"
    fi

    echo "Variable \$foo after if statement: $foo"

    lines="first line\nsecond line\nthird line"

    function my_while_loop
    {

    echo -e $lines | while read line
    do
        if [[ "$line" == "second line" ]]
        then
        foo=2; return 2;
        echo "Variable \$foo updated to $foo inside if inside while loop"
        fi

        echo -e $lines | while read line
do
    if [[ "$line" == "second line" ]]
    then
    foo=2;          
    echo "Variable \$foo updated to $foo inside if inside while loop"
    return 2;
    fi

    # Code below won't be executed since we returned from function in 'if' statement
    # We aready reported the $foo var beint set to 2 anyway
    echo "Value of \$foo in while loop body: $foo"

done
}

    my_while_loop; foo="$?"

    echo "Variable \$foo after while loop: $foo"

    Output:
    Setting  $foo 1
    Variable $foo after if statement: 1
    Value of $foo in while loop body: 1
    Variable $foo after while loop: 2

    bash --version

    GNU bash, version 3.2.51(1)-release (x86_64-apple-darwin13)
    Copyright (C) 2007 Free Software Foundation, Inc.

0voto

Ajay Singh Points 436

Bien que ce soit une vieille question et qu'elle ait été posée plusieurs fois, voici ce que je fais après des heures de bricolage here et la seule option qui a fonctionné pour moi est de stocker la valeur dans un fichier pendant les sous-shells de la boucle while et de la récupérer ensuite. C'est simple.

Utilisez echo pour stocker et cat à récupérer. Et l'utilisateur de bash doit chown le répertoire ou avoir un accès en lecture-écriture chmod accès.

#write to file
echo "1" > foo.txt

while condition; do 
    if (condition); then
        #write again to file
        echo "2" > foo.txt      
    fi
done

#read from file
echo "Value of \$foo in while loop body: $(cat foo.txt)"

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X