थोड़ी देर के लूप के अंदर संशोधित चर याद नहीं किया जाता है


187

निम्नलिखित कार्यक्रम में, अगर मैं $fooपहले ifकथन के अंदर मान 1 के लिए चर सेट करता हूं, तो यह इस अर्थ में काम करता है कि इसका मान यदि कथन के बाद याद किया जाता है। हालाँकि, जब मैंने उसी वैरिएबल को वैल्यू 2 के अंदर सेट किया, ifजो कि whileस्टेटमेंट के अंदर है , तो whileलूप के बाद इसे भूल जाते हैं । यह ऐसा व्यवहार कर रहा है जैसे मैं लूप के $fooअंदर चर की किसी प्रकार की प्रतिलिपि का उपयोग कर रहा हूं whileऔर मैं केवल उस विशेष प्रति को संशोधित कर रहा हूं। यहां पूरा परीक्षण कार्यक्रम है:

#!/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)


शेलचेक उपयोगिता इसे पकड़ती है (देखें github.com/koalaman/shellcheck/wiki/SC2030 ); ऊपर दिए गए कोड को shellcheck.net में काटें और चिपकाएँ, यह फीडबैक लाइन 19 के लिए जारी करता है:SC2030: Modification of foo is local (to subshell caused by pipeline).
qneill

जवाबों:


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

whileपाश एक subshell में मार डाला गया है। एक बार सबमिशन से बाहर हो जाने के बाद आप वैरिएबल में कोई बदलाव नहीं कर पाएंगे।

इसके बजाय आप मुख्य शेल प्रक्रिया में होने के लिए लूप को फिर से लिखने के लिए यहां एक स्ट्रिंग का उपयोग कर सकते हैं ; केवल 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 -e "$lines")"

echoजब आप असाइन करते हैं तो तुरंत बैकस्लैश दृश्यों का विस्तार करके यहां उपर्युक्त स्ट्रिंग में बल्कि बदसूरत से छुटकारा पा सकते हैं lines$'...'के हवाले के रूप वहाँ इस्तेमाल किया जा सकता:

lines=$'first line\nsecond line\nthird line'
while read line; do
    ...
done <<< "$lines"

20
बेहतर परिवर्तन के <<< "$(echo -e "$lines")"लिए सरल<<< "$lines"
beliy

क्या होगा अगर स्रोत tail -fतय पाठ के बजाय से था ?
एमटी ईई

2
@mteee आप उपयोग कर सकते हैं while read -r line; do echo "LINE: $line"; done < <(tail -f file)(जाहिर है कि लूप समाप्त नहीं होगा क्योंकि यह इनपुट से प्रतीक्षा करना जारी रखता है tail)।
पीपी

मैं / bin / sh तक सीमित हूं - क्या कोई वैकल्पिक तरीका है जो पुराने शेल में काम करता है?
user9645

1
@AvinashYadav समस्या वास्तव में whileलूप या forलूप से संबंधित नहीं है ; बल्कि सब-हेल्दी का उपयोग, में cmd1 | cmd2, cmd2एक सब-शेल में है। इसलिए यदि एक forलूप को सब-सब्मिट में निष्पादित किया जाता है, तो अप्रत्याशित / समस्याग्रस्त व्यवहार प्रदर्शित किया जाएगा।
पीपी

48

# 2 UPDATED

स्पष्टीकरण ब्लू मून के जवाब में है।

वैकल्पिक समाधान:

हटा दें echo

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

इको-द-दस्तावेज़ के अंदर प्रतिध्वनि जोड़ें

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

echoपृष्ठभूमि में चलाएं :

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

स्पष्ट रूप से एक फ़ाइल हैंडल पर पुनर्निर्देशित करें (अंतरिक्ष को ध्यान में रखें < <!):

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

या बस पुनर्निर्देशित करें stdin:

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

और एक chepner(को समाप्त करने के लिए echo):

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

$linesएक नया उप-शेल शुरू किए बिना चर को एक सरणी में बदला जा सकता है। वर्ण \और nहै कुछ चरित्र को परिवर्तित किया है (जैसे एक असली नई लाइन चरित्र) और सरणी तत्वों में स्ट्रिंग विभाजित करने के लिए भारतीय विदेश सेवा (आंतरिक फील्ड सेपरेटर) चर का उपयोग करें। इसे इस तरह किया जा सकता है:

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

परिणाम है

first line\nsecond line\nthird line
first line second line third line, Length: 3
arr=([0]="first line" [1]="second line" [2]="third line")

+1-डॉक के लिए, चूंकि linesचर का एकमात्र उद्देश्य whileलूप खिलाना प्रतीत होता है ।
चेपनर

@chepner: Thx! मैंने आपको एक और जोड़ा, जो आपको समर्पित है!
ट्रू

अभी एक और समाधान यहाँ दिया गया है :for line in $(echo -e $lines); do ... done
dma_k

@dma_k आपकी टिप्पणी के लिए धन्यवाद! इस विलयन से एक शब्द में 6 पंक्तियों का परिणाम होगा। ओपी का अनुरोध अलग था ...
TrueY

upvoted। इधर-
उधर एक उप

9

आप इस bash FAQ को पूछने के लिए 742342 वें उपयोगकर्ता हैं उत्तर में पाइपों द्वारा निर्मित उपधाराओं में निर्धारित चर के सामान्य मामले का भी वर्णन है:

E4) यदि मैं एक कमांड के आउटपुट को पाइप करता हूं read variable, $variableतो रीड कमांड खत्म होने पर आउटपुट क्यों नहीं दिखाता है ?

यह यूनिक्स प्रक्रियाओं के बीच माता-पिता के बच्चे के रिश्ते के साथ करना है। यह पाइपलाइनों में चलने वाली सभी कमांड को प्रभावित करता है, न कि केवल साधारण कॉल को read। उदाहरण के लिए, कमांड के आउटपुट को एक whileलूप में पाइप करना जो बार-बार कॉल करता readहै, उसी व्यवहार के परिणामस्वरूप होगा।

पाइपलाइन का प्रत्येक तत्व, यहां तक ​​कि एक बिलिन या शेल फ़ंक्शन, एक अलग प्रक्रिया में चलता है, पाइपलाइन को चलाने वाले शेल का एक बच्चा। एक उप-प्रजाति अपने माता-पिता के वातावरण को प्रभावित नहीं कर सकती है। जब readकमांड वेरिएबल को इनपुट पर सेट करता है, तो वह वेरिएबल केवल सब-शेल में सेट होता है, पैरेंट शेल के लिए नहीं। जब सबस्क्रिप्शन बाहर निकलता है, तो चर का मान खो जाता है।

समाप्त होने वाली कई पाइपलाइनों read variableको कमांड प्रतिस्थापन में परिवर्तित किया जा सकता है, जो एक निर्दिष्ट कमांड के आउटपुट को कैप्चर करेगा। तब आउटपुट को एक चर में सौंपा जा सकता है:

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

में परिवर्तित किया जा सकता है

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

यह दुर्भाग्य से, पाठ को कई चर के बीच विभाजित करने के लिए काम नहीं करता है, जैसा कि कई चर तर्क दिए जाने पर पढ़ा जाता है। यदि आपको ऐसा करने की आवश्यकता है, तो आप आउटपुट को एक चर में पढ़ने के लिए ऊपर दिए गए कमांड प्रतिस्थापन का उपयोग कर सकते हैं और बैश पैटर्न हटाने विस्तार ऑपरेटरों का उपयोग करके चर को काट सकते हैं या निम्नलिखित दृष्टिकोण के कुछ प्रकार का उपयोग कर सकते हैं।

कहो / usr / स्थानीय / बिन / ipaddr निम्नलिखित शेल स्क्रिप्ट है:

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

के बजाय का उपयोग करने का

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

अलग-अलग ओकटेट्स में स्थानीय मशीन के आईपी पते को तोड़ने के लिए उपयोग करें

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

हालांकि, सावधान रहें कि यह शेल के स्थितीय मापदंडों को बदल देगा। यदि आपको उनकी आवश्यकता है, तो आपको ऐसा करने से पहले उन्हें बचाना चाहिए।

यह सामान्य दृष्टिकोण है - ज्यादातर मामलों में आपको $ IFS को एक अलग मूल्य पर सेट करने की आवश्यकता नहीं होगी।

कुछ अन्य उपयोगकर्ता-आपूर्ति वाले विकल्पों में शामिल हैं:

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

और, जहां प्रक्रिया प्रतिस्थापन उपलब्ध है,

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

7
आप फैमिली फ्यूड की समस्या भूल गए । कभी-कभी शब्दों का एक ही संयोजन ढूंढना बहुत कठिन होता है क्योंकि जिसने भी उत्तर लिखा है, ताकि आप न तो गलत परिणामों से भर जाएं, और न ही उत्तर को फ़िल्टर करें।
Evi1M4chine

3

हम्म् ... मैं लगभग शपथ लेता हूं कि यह मूल बॉर्न शेल के लिए काम करता है, लेकिन अभी जांच के लिए एक रनिंग कॉपी तक पहुंच नहीं है।

हालाँकि, समस्या का एक बहुत ही मामूली हल है।

स्क्रिप्ट की पहली पंक्ति को इसमें से बदलें:

#!/bin/bash

सेवा

#!/bin/ksh

ए टी वॉयला! एक पाइपलाइन के अंत में पढ़ा गया यह ठीक काम करता है, यह मानते हुए कि आपके पास कोर्न शेल स्थापित है।


0

कैसे एक बहुत ही सरल विधि के बारे में

    +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.

6
हो सकता है कि यहां सतह के नीचे एक अच्छा जवाब है, लेकिन आपने फॉर्मेटिंग को उस बिंदु तक पहुंचाया है कि कोशिश करना और पढ़ना अप्रिय है।
मार्क अमेरी

आपका मतलब है कि मूल कोड पढ़ने के लिए सुखद है? (मैंने अभी-अभी अनुसरण किया है: p)
मार्सिन

0

यह एक दिलचस्प सवाल है और बॉर्न शेल में एक बहुत ही मूल अवधारणा को छूता है और उप-प्रकार है। यहां मैं एक समाधान प्रदान करता हूं जो पिछले समाधानों से अलग है किसी प्रकार का फ़िल्टरिंग करके। मैं एक उदाहरण दूंगा जो वास्तविक जीवन में उपयोगी हो सकता है। यह जाँच के लिए एक टुकड़ा है कि डाउनलोड की गई फाइलें एक ज्ञात चेकसम के अनुरूप हैं। चेकसम फ़ाइल निम्नलिखित की तरह दिखती है (केवल 3 लाइनें दिखाते हुए):

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

शेल स्क्रिप्ट:

#!/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

अभिभावक और उपखंड गूंज कमांड के माध्यम से संवाद करते हैं। आप मूल शेल के लिए पाठ को पार्स करने के लिए कुछ आसान चुन सकते हैं। यह तरीका आपकी सामान्य सोच को नहीं तोड़ता है, बस आपको कुछ पोस्ट प्रोसेसिंग करनी होती है। ऐसा करने के लिए आप grep, sed, awk, और अधिक का उपयोग कर सकते हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.