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

dans le programme suivant, si je mets la variable $foo à la valeur 1 à l'intérieur de la première instruction if , cela fonctionne dans le sens que sa valeur est rappelée après l'instruction if. Cependant, quand je mets la même variable à la valeur 2 à l'intérieur d'une boucle if qui est à l'intérieur d'une instruction while , elle est oubliée après la boucle while . Il se comporte comme si j'utilisais une sorte de copie de la variable $foo à l'intérieur de la boucle while et je suis modification de cette copie. 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 linensecond linenthird 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)
127
demandé sur codeforester 2013-05-31 13:38:24

6 réponses

echo -e $lines | while read line 
...
done

la boucle while est exécutée dans un sous-puits. Ainsi, tout changement que vous faites à la variable ne sera pas disponible une fois que le sous-puits sort.

à la place, vous pouvez utiliser un ici chaîne pour réécrire la boucle while à être dans le processus de shell principal; seulement echo -e $lines s'exécutera dans une sous-coquille:

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 -e "$lines")"
171
répondu P.P. 2017-08-11 15:19:39

mise à JOUR#2

explication est dans la réponse de Blue Moons.

solutions Alternatives:

Eliminer echo

while read line; do
...
done <<EOT
first line
second line
third line
EOT

Ajouter de l'écho à l'intérieur de l'ici-est-la-document

while read line; do
...
done <<EOT
$(echo -e $lines)
EOT

Exécuter echo en arrière-plan:

coproc echo -e $lines
while read -u ${COPROC[0]} line; do 
...
done

rediriger vers un gestionnaire de fichier explicitement (Mind l'espace dans < < !):

exec 3< <(echo -e  $lines)
while read -u 3 line; do
...
done

ou tout simplement rediriger vers le stdin :

while read line; do
...
done < <(echo -e  $lines)

et un pour chepner (éliminant echo ):

arr=("first line" "second line" "third line");
for((i=0;i<${#arr[*]};++i)) { line=${arr[i]}; 
...
}

Variable $lines peut être converti en un tableau sans démarrer un nouveau sous-shell. Les caractères \ et n doivent être convertis en un certain caractère (par exemple un vrai caractère de nouvelle ligne) et utiliser L'IFS (Internal Field Separator).) variable pour diviser la chaîne en éléments de tableau. Cela peut être fait comme:

lines="first line\nsecond line\nthird line"
echo "$lines"
OIFS="$IFS"
IFS=$'\n' arr=(${lines//\n/$'\n'}) # Conversion
IFS="$OIFS"
echo "${arr[@]}", Length: ${#arr[*]}
set|grep ^arr

le Résultat est

first line\nsecond line\nthird line
first line second line third line, Length: 3
arr=([0]="first line" [1]="second line" [2]="third line")
41
répondu TrueY 2015-10-30 13:50:59

vous êtes le 742342nd utilisateur de demander cette FAQ bash. la réponse décrit également le cas général des variables définies dans les sous-cellules créées par pipes:

E4) si je pipe la sortie d'une commande dans read variable , pourquoi la sortie n'apparaît-elle pas dans $variable lorsque la commande read est terminée?

cela a à voir avec la relation parent-enfant entre Unix processus. Elle affecte toutes les commandes exécutées dans les canalisations, et pas seulement appels simples à read . Par exemple, raccorder la sortie d'une commande dans une boucle while qui appelle à plusieurs reprises read se traduira par le même comportement.

chaque élément d'un pipeline, même une fonction builtin ou shell, fonctionne dans un processus séparé, un enfant de la coquille pipeline. Un sous-processus ne peut pas affecter l'environnement de la société mère. Quand la commande read définit la variable à l'entrée, qui la variable est définie seulement dans le sous-puits, pas dans la couche mère. Lorsque la valeur de la variable est perdue.

plusieurs pipelines qui se terminent par read variable peuvent être convertis dans les substitutions de commande, qui captureront la sortie de une commande spécifiée. La sortie peut alors être affecté à un variable:

grep ^gnu /usr/lib/news/active | wc -l | read ngroup

peut être converti en

ngroup=$(grep ^gnu /usr/lib/news/active | wc -l)

cela ne fonctionne pas, malheureusement, de diviser le texte entre plusieurs variables, comme lu lorsque plusieurs variables argument. Si vous avez besoin de le faire, vous pouvez utiliser substitution de commande ci-dessus pour lire la sortie dans une variable et découper la variable à l'aide de la suppression du motif bash les opérateurs d'expansion ou d'utiliser une variante des suivants approche.

Dire /usr/local/bin/ip est le script suivant:

#! /bin/sh
host `hostname` | awk '/address/ {print $NF}'

au lieu d'utiliser

/usr/local/bin/ipaddr | read A B C D

pour fractionner l'adresse IP de la machine locale en octets séparés, utilisez

OIFS="$IFS"
IFS=.
set -- $(/usr/local/bin/ipaddr)
IFS="$OIFS"
A="" B="" C="" D=""

attention, cependant, que cela va changer la position du shell paramètre. Si vous avez besoin d'eux, vous devriez les sauver avant de faire ce.

C'est l'approche générale -- dans la plupart des cas, vous n'aurez pas besoin de définissez $IFS à une valeur différente.

parmi les autres solutions proposées par l'utilisateur, on peut citer:

read A B C D << HERE
    $(IFS=.; echo $(/usr/local/bin/ipaddr))
HERE

et, lorsqu'une substitution de procédé est disponible,

read A B C D < <(IFS=.; echo $(/usr/local/bin/ipaddr))
9
répondu Jens 2016-09-10 19:15:41

Hmmm... Je jurerais presque que cela a fonctionné pour le shell Bourne original, mais je n'ai pas accès à une copie en cours d'exécution juste maintenant pour vérifier.

il y a, cependant, une solution très triviale au problème.

modifier la première ligne du script de:

#!/bin/bash

à

#!/bin/ksh

Et le tour est joué! Une lecture à la fin d'un pipeline fonctionne très bien, en supposant que vous avez la coquille Korn installée.

2
répondu Jonathan Quist 2014-10-20 18:56:41

c'est une question intéressante et touche un concept très basique dans Bourne shell et 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 pour vérifier la conformité des fichiers téléchargés pour connaître les sommes de contrôle. Le fichier de somme de contrôle ressemble au suivant (Ne montrant que 3 lignes):

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

le script shell:

#!/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 }')
    num2=$(echo $line | awk '{print }')
    fname=$(echo $line | awk '{print }')
    if [ -f "$fname" ]; then
        res=$(sum $fname)
        filegood=$(sum $fname | awk -v na=$num1 -v nb=$num2 -v fn=$fname '{ if (na ==  && nb == ) { 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 la cellule communiquent par la commande echo. Vous pouvez choisir des textes faciles à analyser pour le shell parent. Cette méthode ne brise pas votre façon normale de penser, juste que vous devez faire un peu de post-traitement. Vous pouvez utiliser grep, sed, awk, et plus pour le faire.

0
répondu Kemin Zhou 2018-03-07 04:02:28

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.
-1
répondu Marcin 2014-08-30 03:54:58