[bash] while 루프 안에서 수정 된 변수는 기억되지 않습니다

다음 프로그램 $foo에서 첫 번째 if명령문 내 에서 변수 를 값 1로 설정 하면 if 문 뒤에 값이 기억된다는 의미에서 작동합니다. 그러나 명령문 if내에있는 내부의 값 2로 동일한 변수를 설정 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)



답변

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

while루프 서브 쉘 실행된다. 따라서 서브 쉘이 종료되면 변수에 대한 변경 사항을 사용할 수 없습니다.

대신 here 문자열 을 사용 하여 while 루프를 기본 셸 프로세스에 있도록 다시 작성할 수 있습니다. 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"


답변

업데이트 된 # 2

설명은 Blue Moons의 답변에 있습니다.

대체 솔루션 :

죽이다 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이 어떤 문자로 변환 (예 : 실제 새 라인 문자) 및 배열 요소로 문자열을 분할하는 IFS (내부 필드 구분) 변수를 사용합니다. 이것은 다음과 같이 수행 할 수 있습니다 :

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")


답변

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)

불행히도 이것은 여러 변수 인수가 주어 졌을 때 읽기처럼 텍스트를 여러 변수로 나누는 작업이 아닙니다. 이 작업을 수행해야하는 경우 위의 명령 대체를 사용하여 출력을 변수로 읽고 bash 패턴 제거 확장 연산자를 사용하여 변수를 잘라내거나 다음 방법의 변형을 사용할 수 있습니다.

/ usr / local / bin / ipaddr이 다음 쉘 스크립트라고 가정하십시오.

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

사용하는 대신

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

로컬 컴퓨터의 IP 주소를 별도의 옥텟으로 나누려면

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))

답변

흠 … 나는 이것이 원래 Bourne 쉘에서 효과가 있다고 맹세하지만 지금은 실행중인 사본에 액세스 할 수는 없습니다.

그러나이 문제에 대한 간단한 해결 방법이 있습니다.

스크립트의 첫 번째 줄을 다음에서 변경하십시오.

#!/bin/bash

#!/bin/ksh

짜잔! Korn 쉘이 설치되어 있다고 가정하면 파이프 라인 끝의 읽기는 정상적으로 작동합니다.


답변

아주 간단한 방법은 어떻습니까

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


답변

이것은 흥미로운 질문이며 Bourne 쉘과 서브 쉘의 매우 기본적인 개념을 다루고 있습니다. 여기서는 일종의 필터링을 수행하여 이전 솔루션과 다른 솔루션을 제공합니다. 나는 실제 생활에 도움이 될만한 예를 제시 할 것입니다. 다운로드 한 파일이 알려진 체크섬을 준수하는지 확인하기위한 조각입니다. 체크섬 파일은 다음과 같습니다 (단 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

부모와 서브 쉘은 echo 명령을 통해 통신합니다. 부모 셸에 대해 구문 분석하기 쉬운 텍스트를 선택할 수 있습니다. 이 방법은 일반적인 사고 방식을 깨뜨리지 않으며 사후 처리를 수행해야합니다. grep, sed, awk 등을 사용하여 수행 할 수 있습니다.


답변