전역 변수가 비정상적으로 동작하게 만드는 것은 while 루프입니까, 아니면 파이프라인입니까?

전역 변수가 비정상적으로 동작하게 만드는 것은 while 루프입니까, 아니면 파이프라인입니까?

COUNTER누군가가 (내 관점에서) 다음 코드에서 변수의 이상한 동작을 설명해 주시겠습니까 ?

#!/bin/bash

COUNTER=0

function increment {
    ((COUNTER++))
}

function report {
    echo "COUNTER: $COUNTER ($1)"
}

function reset_counter {
    COUNTER=0
}

function increment_if_yes {
    answer=$1
    if [ "$answer" == "yes" ]
    then
        increment
    fi
}

function break_it {
    echo -e "maybe\nyes\nno" | \
    while read LL
    do
        increment_if_yes $LL
    done    
}

report start # counter should be 0
increment
report one
increment_if_yes yes
report two
increment_if_yes no
report "still two"

reset_counter
report reset

break_it
report "I'd expect one"

나는 그것이 스크립트의 끝에 COUNTER있기를 원하지만 그것은 다음과 같습니다 :10

$ ./broken_variable.sh 
COUNTER: 0 (start)
COUNTER: 1 (one)
COUNTER: 2 (two)
COUNTER: 2 (still two)
COUNTER: 0 (reset)
COUNTER: 0 (I'd expect one)

답변1

OP의 현재 코드는 에서 예상대로 작동하며 ksh다른 셸에서도 작동할 수 있지만 작동하지 않을 수 있습니다 bash.

루프가 breakit()자식 프로세스 내에서 실행되도록 합니다. 이는 결국 while 루프의 모든 함수 호출이 자식 프로세스 내에서도 실행된다는 것을 의미합니다.echo ... | while ...while

하위 프로세스( while이 경우 루프)는복사변수 COUNTER이므로 하위 프로세스의 변경 사항은 COUNTER다음에만 적용됩니다.복사변하기 쉬운. 자식 프로세스가 종료되면복사분실 되었습니다 COUNTER. 제어권이 상위 프로세스로 반환되면 (원래) COUNTER변수는 하위 프로세스를 시작하기 전과 동일한 값을 갖습니다.

while원하는 동작을 달성하려면 루프가 상위 프로세스에서 실행되는지 확인해야 합니다 . 프로세스 대체를 사용하는 한 가지 방법:

while read LL
do
    increment_if_yes "$LL"
done < <( echo -e "maybe\nyes\nno" )

답변2

이 간단한 예가 도움이 될 수 있습니다.

$ c=0
$ printf 'a\nb\nc\n' | while read i; do (( c++ )); echo "c is now $c"; done
c is now 1
c is now 2
c is now 3
$ echo "$c"
0

보시다시피 이는 스크립트에서 관찰한 동작을 재현합니다. 그 이유는 데이터를 에 파이핑하기 때문에 while모든 상위 변수의 복사본을 상속하는 하위 쉘이 시작되지만 루프가 종료되면 해당 복사본을 상위 변수로 다시 내보내지 않는다는 의미이기 때문입니다. 즉, COUNTER변수를 증가시키지 않고 변수의 복사본을 증가시키며, 이 복사본은 루프가 끝난 후 즉시 삭제됩니다.

수정된 버전의 스크립트를 사용해 보면 실제로 작동하는 것을 볼 수 있습니다.

#!/bin/bash

COUNTER=0

function increment {
  echo "increment called"
    ((COUNTER++))
}

function report {
    echo "COUNTER: $COUNTER ($1)"
}

function reset_counter {
    COUNTER=0
}

function increment_if_yes {
    answer=$1
    if [ "$answer" == "yes" ]
    then
        increment
    fi
}

function break_it {
  echo "aa COUNTER at start of break_it: $COUNTER"
    echo -e "maybe\nyes\nno" | \
    while read LL
    do
        echo "bb COUNTER in loop top: $COUNTER"
        increment_if_yes $LL
        echo "bb COUNTER in loop bottom: $COUNTER"
    done
    echo "aa COUNTER at end of break_it: $COUNTER"
}

report start # counter should be 0
increment
report one
increment_if_yes yes
report two
increment_if_yes no
report "still two"

reset_counter
report reset

break_it
report "I'd expect one"

이 인쇄를 실행하십시오.

COUNTER: 0 (start)
increment called
COUNTER: 1 (one)
increment called
COUNTER: 2 (two)
COUNTER: 2 (still two)
COUNTER: 0 (reset)
aa COUNTER at start of break_it: 0
bb COUNTER in loop top: 0
bb COUNTER in loop bottom: 0
bb COUNTER in loop top: 0
increment called
bb COUNTER in loop bottom: 1
bb COUNTER in loop top: 1
bb COUNTER in loop bottom: 1
aa COUNTER at end of break_it: 0
COUNTER: 0 (I'd expect one)

스크립트의 나머지 부분에서 사용할 수 있는 변수가 아니라 실제로 해당 변수의 복사본이라는 점을 제외하면 함수 에서 bb COUNTER명명된 변수가 증가한다는 값이 어떻게 표시되는지 확인하세요 .$COUNTERbreak_it

마지막으로 bash 매뉴얼의 명령 실행 환경 섹션을 자세히 읽어볼 수도 있습니다. 특히 다음을 강조합니다.

내장된 기능이나 쉘 기능 이외의 간단한 명령을 실행하고 싶을 때,별도의 실행 환경에서 호출됩니다.여기에는 다음이 포함됩니다. 달리 명시하지 않는 한 이러한 값은 셸에서 상속됩니다.

  • 쉘의 열린 파일 및 명령으로 리디렉션하여 지정된 수정 사항 및 추가 사항
  • 현재 작업 디렉토리
  • 파일 생성 모드 마스크
  • 내보내기용으로 표시된 셸 변수 및 함수및 명령을 위해 내보낸 변수가 환경에 전달됩니다(환경 참조).
  • 쉘이 포착한 트랩은 쉘의 부모로부터 상속된 값으로 재설정되고 쉘이 무시한 트랩은 무시됩니다.

이 별도의 환경에서 호출되는 명령은 영향을 미치지 않습니다. 쉘의 실행 환경.

마지막 문장이 문제의 핵심입니다. 별도의 환경에서 호출된 명령은 상위 환경에 영향을 미칠 수 없으므로 원하는 방식으로 변수를 증가시킬 수 없습니다.

그러나 쉘(bash)이 지원하므로 가능합니다.프로세스 교체, 기능을 다음과 같이 변경하면 작동합니다.

 function break_it {
    while read LL
    do
        increment_if_yes $LL
    done < <(printf 'maybe\nyes\nno\n')
}

이제 원본 스크립트를 실행하고 break_it위에 표시된 대로 함수를 수정하면 다음과 같은 결과를 얻습니다.

$ foo.sh 
COUNTER: 0 (start)
COUNTER: 1 (one)
COUNTER: 2 (two)
COUNTER: 2 (still two)
COUNTER: 0 (reset)
COUNTER: 1 (I'd expect one)

관련 정보