왜 내 변수가 하나의 'while read'루프에서 로컬이지만 다른 것처럼 보이는 다른 루프에서는 로컬입니까?


25

$x아래 스 니펫에서 다른 값을 얻는 이유는 무엇 입니까?

#!/bin/bash

x=1
echo fred > junk ; while read var ; do x=55 ; done < junk
echo x=$x 
#    x=55 .. I'd expect this result

x=1
cat junk | while read var ; do x=55 ; done
echo x=$x 
#    x=1 .. but why?

x=1
echo fred | while read var ; do x=55 ; done
echo x=$x 
#    x=1  .. but why?

답변:


26

올바른 설명은 이미 jsbillingsgeekosaur에 의해 제공 되었지만 조금 더 확장하겠습니다.

bash를 포함한 대부분의 셸에서 파이프 라인의 각 측면은 하위 셸에서 실행되므로 셸의 내부 상태 변경 (예 : 변수 설정)은 파이프 라인의 해당 세그먼트에 국한됩니다. 서브 쉘에서 얻을 수있는 유일한 정보는 출력 (표준 출력 및 기타 파일 디스크립터로)과 종료 코드 (0에서 255 사이의 숫자)입니다. 예를 들어 다음 코드 조각은 0을 인쇄합니다.

a=0; a=1 | a=2; echo $a

ksh (pdksh / mksh 변형이 아닌 AT & T 코드에서 파생 된 변형) 및 zsh에서 파이프 라인의 마지막 항목은 상위 쉘에서 실행됩니다. (POSIX는 두 가지 동작을 모두 허용합니다.) 따라서 위의 스 니펫은 2를 인쇄합니다.

유용한 관용구는 파이프 라인에 while 루프 (또는 파이프 라인의 오른쪽에있는 모든 것을 포함하지만 실제로는 while 루프가 일반적 임)의 연속을 포함하는 것입니다.

cat junk | {
  while read var ; do x=55 ; done
  echo x=$x 
}

1
감사합니다 Gilles .. 그 a = 0; a = 1 | a = 2는 매우 명확한 그림을 제공합니다. 내부 상태의 지역화뿐만 아니라 파이프 라인이 파이프를 통해 실제로 파이프를 통해 아무것도 보내지 않아도됩니다 (종료 코드 (?) 제외). 파이프에 대한 흥미로운 통찰력입니다 ... < <(locate -ber ^\.tag$)원래 약간 불분명 한 답변과 geekosaur 및 glenn jackman의 comemnts 덕분에 스크립트를 실행할 수 있었습니다. 나는 처음에 대답을 받아들이는 것에 대한 딜레마에 빠졌지 만 nett 결과 특히 jsbillings 후속 의견과 함께 매우 분명했습니다 :)
Peter.O

함수에 파이프 된 것처럼 느껴지므로 일부 변수와 테스트를 그 내부로 옮겼습니다.
물병 자리 힘

8

가변 범위 문제가 발생했습니다. 파이프의 오른쪽에있는 while 루프에 정의 된 변수에는 고유 한 로컬 범위 컨텍스트가 있으며 변수의 변경 사항은 루프 외부에 표시되지 않습니다. while 루프는 기본적으로 쉘 환경 의 사본 을 얻는 서브 쉘이며 환경의 모든 변경 사항은 쉘 끝에서 유실됩니다. 이 StackOverflow 질문을 참조하십시오 .

업데이트 : 자체 루프가있는 while 루프가 파이프의 끝점이기 때문에 중요한 사실을 지적하지 않았습니다. 대답에서 업데이트했습니다.


@jsbillings은 .. 좋아, 두 마지막 조각을 설명하고 있지만, 루프에 $ X 설정 값이로 수행되는 경우, 먼저 설명하지 못한다 (55) (루프 '하면서'의 범위를 넘어)을
Peter.O

5
@ fred.bear : while루프를 서브 쉘에 던지는 파이프 라인의 꼬리 끝으로 실행하고 있습니다.
geekosaur

2
이것은 bash 프로세스 대체가 작용하는 곳입니다. 대신에 blah|blah|while read ...,while read ...; done < <(blah|blah)
glenn jackman

1
@geekosaur : 내 대답에 포함하지 않은 세부 사항을 작성해 주셔서 감사합니다.
jsbillings

1
-1 죄송하지만이 답변은 잘못되었습니다. 이 기능이 많은 프로그래밍 언어에서는 작동하지만 쉘에서는 작동하지 않는 방법에 대해 설명합니다. @Gilles는 아래에 있습니다.
JPC

6

마찬가지로 다른 답변에서 언급 한 수정이 주요 쉘에 표시가되지 않습니다 만든 있도록, 파이프 라인의 부분은 서브 쉘에서 실행됩니다.

Bash 만 고려하면 cmd | { stuff; more stuff; }구조 외에 두 가지 다른 해결 방법이 있습니다 .

  1. 프로세스 대체 에서 입력을 경로 재 지정하십시오 .

    while read var ; do x=55 ; done < <(echo fred)
    echo "$x"
    

    명령의 출력 <(...)은 마치 명명 된 파이프 인 것처럼 보이도록 만들어집니다.

  2. lastpipeKSH 같은 배쉬 일을하게하고, 주요 쉘 프로세스에서 파이프 라인의 마지막 부분을 실행하는 옵션. 작업 제어가 비활성화 된 경우에만 작동하지만 대화 형 셸에서는 작동하지 않습니다.

    bash -c '
      shopt -s lastpipe
      echo fred | while read var ; do x=55 ; done; 
      echo "$x"
    '
    

    또는

    bash -O lastpipe -c '
      echo fred | while read var ; do x=55 ; done; 
      echo "$x"
    '
    

프로세스 대체는 물론 ksh 및 zsh에서도 지원됩니다. 그러나 어쨌든 메인 쉘에서 파이프 라인의 마지막 부분을 실행하기 때문에 실제로이를 해결 방법으로 사용할 필요는 없습니다.


0
#!/bin/bash
set -x

# prepare test data.
mkdir -p ~/test_var_global
cd ~/test_var_global
echo "a"> core.1
echo "b"> core.2
echo "c"> core.3


var=0

coreFiles=$(find . -type f -name "core*")
while read -r file;
do
  # perform computations on $i
  ((var++))
done <<EOF
$coreFiles
EOF

echo $var

Result:
...
+ echo 3
3

작동 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.