কিছুক্ষণের মধ্যে পরিবর্তিত একটি পরিবর্তনশীল লুপ মনে রাখে না


186

নিম্নলিখিত প্রোগ্রামে আমি যদি $fooপ্রথম ifস্টেটমেন্টের ভিতরে ভ্যারিয়েবল 1 এর মান নির্ধারণ করি তবে এটি এই অর্থে কাজ করে যে এর মান যদি if স্টেটমেন্টের পরে মনে থাকে। যাইহোক, আমি যখন স্টেটমেন্টের ifঅভ্যন্তরে থাকা 2 এর মান 2 তে একই চলকটি সেট করি তখন whileএটি whileলুপের পরে ভুলে যায় forgotten এটি এমন আচরণ করছে যে আমি লুপের $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 দেখুন ); শেলচেক.নেমে উপরের কোডটি কেটে পেস্ট করা লাইন 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"
বেলি

যদি উত্স tail -fস্থির পাঠ্যের পরিবর্তে থেকে হয়?
এমটি আইই

2
@ মিটিই আপনি ব্যবহার করতে পারেন while read -r line; do echo "LINE: $line"; done < <(tail -f file)(স্পষ্টতই লুপটি শেষ হবে না কারণ এটি থেকে ইনপুটটির জন্য অপেক্ষা করা অব্যাহত থাকবে tail)।
পিপি

আমি / বিন / শ-সীমাবদ্ধ - কোনও বিকল্প উপায় কি পুরানো শেলটিতে কাজ করে?
user9645

1
@ অভিনাশয়াদব সমস্যাটি আসলে whileলুপ বা forলুপের সাথে সম্পর্কিত নয় ; বরং সাবশেলের ব্যবহার যেমন, ইন cmd1 | cmd2, cmd2একটি সাব-শেলের মধ্যে। সুতরাং যদি একটি forলুপ একটি সাবশেলে কার্যকর করা হয়, অপ্রত্যাশিত / সমস্যাযুক্ত আচরণ প্রদর্শিত হবে।
পিপি

48

# 2 আপডেট

ব্লু মুনসের উত্তরে ব্যাখ্যা রয়েছে।

বিকল্প সমাধান:

নিষ্কাশন করা 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লুপটি খাওয়ানো বলে মনে হয় ।
চ্যানার

@ চেপার: থেক্স! আমি আরও একটি যুক্ত করেছি, আপনাকে উত্সর্গীকৃত!
সত্য

এখানে আরও একটি সমাধান দেওয়া আছে :for line in $(echo -e $lines); do ... done
dma_k

@dma_k আপনার মন্তব্যের জন্য ধন্যবাদ! এই সমাধানটির ফলে একটি শব্দ যুক্ত 6 টি লাইন থাকবে।
ওপির

upvoted। এখানে একটি সাবসেলের মধ্যে প্রতিধ্বনি চলছিল, ছাইতে কাজ করা কয়েকটি সমাধানগুলির মধ্যে একটি ছিল
হামি

9

আপনি এই ব্যাশ FAQ জিজ্ঞাসা করার জন্য 742342 তম ব্যবহারকারী উত্তরটি পাইপ দ্বারা নির্মিত সাব-হিটগুলিতে সেট করা ভেরিয়েবলগুলির সাধারণ ক্ষেত্রেও বর্ণনা করে:

E4) আমি যদি কোনও কমান্ডের আউটপুটটি পাইপ করি তবে পঠিত কমান্ড শেষ হলে read variableআউটপুট কেন প্রদর্শিত হবে না $variable?

এটি ইউনিক্স প্রক্রিয়াগুলির মধ্যে পিতা-সন্তানের সম্পর্কের সাথে সম্পর্কযুক্ত। এটা তোলে পাইপলাইনগুলি চালানোর সব আদেশ, না শুধু সহজ কল প্রভাবিত read। উদাহরণস্বরূপ, whileবারবার কল করে একটি লুপে কমান্ডের আউটপুটটি পাইপ করা readএকই আচরণের ফলস্বরূপ।

পাইপলাইনের প্রতিটি উপাদান এমনকি বিল্টিন বা শেল ফাংশন পৃথক প্রক্রিয়াতে চালিত হয়, খোলের একটি শিশু পাইপলাইন চালাচ্ছে। একটি উপ-প্রসেস তার পিতামাতার পরিবেশকে প্রভাবিত করতে পারে না। readকমান্ডটি যখন ভেরিয়েবলটিকে ইনপুটে সেট করে, তখন সেই পরিবর্তনশীলটি কেবল সাব-শেলের মধ্যে সেট করা হয়, প্যারেন্ট শেল নয়। সাবশেলটি প্রস্থান করলে ভেরিয়েবলের মান হারাবে is

সমাপ্ত অনেক পাইপলাইন 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"

তবে সাবধান থাকুন যে এটি শেলের অবস্থানগত পরামিতিগুলিকে পরিবর্তন করবে। আপনার যদি তাদের প্রয়োজন হয় তবে এটি করার আগে আপনার সেভ করা উচিত।

এটি সাধারণ পন্থা - বেশিরভাগ ক্ষেত্রে আপনাকে FS আইএফএসের কোনও আলাদা মান নির্ধারণ করতে হবে না।

ব্যবহারকারীর দ্বারা সরবরাহিত কিছু বিকল্পের মধ্যে রয়েছে:

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
সম্ভবত এখানে পৃষ্ঠতল অধীনে একটি শালীন উত্তর আছে, কিন্তু আপনি বিন্যাসটি এতক্ষণে কসরত করেছেন যে এটি চেষ্টা এবং পড়া অপ্রীতিকর।
মার্ক আমেরিকা

আপনি বলতে চাচ্ছেন আসল কোডটি পড়ে সুখকর? (আমি সবেমাত্র অনুসরণ করেছি: পি)
মার্সিন

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

পিতামাতা এবং সাবশেল ইকো কমান্ডের মাধ্যমে যোগাযোগ করে। প্যারেন্ট শেলটির জন্য আপনি কিছু সহজে পাঠ্যকে বেছে নিতে পারেন। এই পদ্ধতিটি আপনার সাধারণ চিন্তাভাবনা ভঙ্গ করে না, কেবলমাত্র আপনাকে কিছু পোস্ট প্রসেসিং করতে হবে। আপনি এটি করার জন্য গ্রেপ, সেড, অ্যাজক এবং আরও অনেক কিছু ব্যবহার করতে পারেন।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.