bash : 읽기 루프가 끝나면 변수가 값을 잃습니다.


36

쉘 스크립트 중 하나에 문제가 있습니다. 동료 몇 명에게 물어 보았지만 모두 머리를 흔드는 것 (긁힘 후)으로 답을 찾아 왔습니다.

내 이해에 따르면 다음 쉘 스크립트는 "Count is 5"를 마지막 줄로 인쇄해야합니다. 그렇지 않은 경우를 제외하고. "Count is 0"을 인쇄합니다. "while read"가 다른 종류의 루프로 바뀌면 제대로 작동합니다. 스크립트는 다음과 같습니다.

에코 "1"> 입력 데이터
에코 "2">> 입력 데이터
에코 "3">> 입력 데이터
에코 "4">> 입력 데이터
에코 "5">> 입력 데이터

CNT = 0 

고양이 input.data | 읽는 동안;
해야 할 것
  CNT ++하자;
  echo "$ CNT에 계산"
끝난 
echo "개수는 $ CNT입니다"

왜 이런 일이 발생하며 어떻게 방지 할 수 있습니까? Debian Lenny와 Squeeze에서 동일한 결과 (예 : bash 3.2.39 및 bash 4.1.5)에서 이것을 시도했습니다. 쉘 스크립트 마법사가 아니라는 것을 완전히 인정하므로 모든 의견을 부탁드립니다.

답변:


30

인수 @ Bash FAQ 항목 # 24 : "루프에 변수를 설정했습니다. 루프가 종료 된 후 변수가 갑자기 사라지는 이유는 무엇입니까? 또는 데이터를 읽을 수없는 파이프는 무엇입니까?" (가장 최근에 여기 에 보관 됨 ).

요약 : 이것은 bash 4.2 이상에서만 지원됩니다. bash를 사용하는 경우 파이프 대신 명령 대체와 같은 다른 방법을 사용해야합니다.


당신의 대답이 가장 광범위한 옵션을 제공했기 때문에 당신은 보너스를 얻습니다.
wolfgangsz

5
연결이 끊어졌습니다. 이것이 링크 전용 답변이 나쁜 이유입니다. 최소한 여기에 답을 요약하십시오.
rudolfbyker

하나님, ksh가 훨씬 더 나은 또 다른 시간 ... 왜, 왜 모두가 bash를 몰아 쳤습니까?
Florian Heigl

@ FlorianHeigl : ksh가 하나의 진정한 쉘이라고 주장하고 있습니까?
이그나시오 바스케스-아 브람스

@ IgnacioVazquez-Abrams 아니오, 그러나 bash에서 while 루프 처리는 끔찍한 PITA라고 주장합니다. 루프 핸들링은 1993 년의 기능을 따라 잡지 못하게하는 오랜 주자였습니다. 다른 것들은 (또한 1993) 내장 핸들러가 간단하고 유능한 getopt 처리입니다. 즉, docopt를 사용하지 않으면 여전히 얻을 수없는 것입니다. 나는 배쉬가 20 년 넘게 끊임없는 노력을 기울이고 있다고 주장하고 있으며, 여기에서이 시간이나 수백만의 나쁜 getopts 사용에 소요 된 시간은 측정 할 수없는 수준이라고 주장한다.
Florian Heigl

30

이것은 일종의 '일반적인'실수입니다. 파이프는 SubShell을 작성하므로 while read스크립트와 다른 쉘에서 실행 되므로 CNT변수가 변경되지 않습니다 (파이프 서브 쉘 내부의 변수 만).

마지막 echo으로 하위 셸 while을 그룹화하여 수정하십시오 (고칠 수있는 다른 방법이 많이 있습니다. 이것은 하나입니다. Iain과 Ignacio의 답변에는 다른 것이 있습니다.)

CNT=0

 cat input.data | ( while read 
do
  let CNT++;
  echo "Counting to $CNT"
done 
echo "Count is $CNT" )

긴 설명 :

  1. CNT스크립트에서 값 0으로 선언 합니다.
  2. SubShell은에서 시작 |됩니다 while read.
  3. 귀하의 $CNT변수는 값이 0 인 서브 쉘에 수출되고;
  4. SubShell은 CNT값을 세고 5로 증가시킵니다 .
  5. SubShell은 변수와 값이 소멸됩니다 (호출 프로세스 / 스크립트로 돌아 가지 않음).
  6. 당신은 당신 echo의 원래 CNT값 0입니다.

2
내가 쓴 첫 번째 셸 스크립트는 나에게 같은 문제를 주며 파이프가 추가 셸을 생성한다는 것을 알기 전에 잠시 벽에 머리를 부딪쳤다. 파이프에서 엉망인 변수는 파이프가 끝나 자마자 범위를 벗어날 것입니다. 즉, 실제로 파이프가 사용 된 파이프 외부에서 변수를 사용하여 무언가를 수행하려면 실제로해야합니다. 임시 파일처럼 펑키 한 것을 통해 상태를 유지하십시오.
photoionized

유감스럽게도 안타깝게도 하나의 수락 보너스 만 줄 수 있습니다. 죄송합니다.
wolfgangsz

10

이 작동합니다

CNT=0 

while read ;
do
  let CNT++;
  echo "Counting to $CNT"
done <input.data
echo "Count is $CNT"

필요한 데이터가 어디에 있는지 알고 다시 가져 오기만하면되기 때문에 현명한 방법입니다. 당신이 높은 기술 솔루션을 모른다면, 당신은 항상 hahahha "파일을 읽을"수 있습니다. 당신을 위해 +1.
erm3nda

1
이것을 읽는 사람이라면, Iain이 제공하는 솔루션은 스크립트가 #! / bin / bash의 첫 줄 (#! / bin / bash)을 가지고 명시 적으로 bash를 호출 할 때만 작동한다는 점에 유의하십시오. #! / bin / sh는 작동하지 않습니다.
Roadowl

1
흥미롭고 첫 번째 예 는 Cat쓸모없는 사용이 실제로 코드 작동을 방해 하는 위치를 보았습니다 . @Roadowl은 유일하게 bashism 은 POSIX 호환 산술 확장let CNT++ 을 사용해야 하는 라인 입니다 . 그것의 나머지는 이미 휴대용입니다. CNT="$((CNT+1))"
와일드 카드

6

while 루프 이전의 파일처럼 하위 셸에서 데이터를 대신 전달하십시오. 이것은 lain의 솔루션과 유사하지만 간헐적 인 파일을 원하지 않는다고 가정합니다.

total=0
while read var
do
  echo "variable: $var"
  ((total+=var))
done < <(echo 45) #output from a command, script, or function
echo "total: $total"
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.