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
있기를 원하지만 그것은 다음과 같습니다 :1
0
$ ./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
명명된 변수가 증가한다는 값이 어떻게 표시되는지 확인하세요 .$COUNTER
break_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)