Bash 루프의 카운터 증가가 작동하지 않습니다.


125

루프를 실행하고 있고 COUNTER. 카운터가 업데이트되지 않는 이유를 알 수 없습니다. 생성되는 서브 쉘 때문입니까? 이 문제를 어떻게 해결할 수 있습니까?

#!/bin/bash

WFY_PATH=/var/log/nginx
WFY_FILE=error.log
COUNTER=0
grep 'GET /log_' $WFY_PATH/$WFY_FILE | grep 'upstream timed out' | awk -F ', ' '{print $2,$4,$0}' | awk '{print "http://domain.com"$5"&ip="$2"&date="$7"&time="$8"&end=1"}' | awk -F '&end=1' '{print $1"&end=1"}' |
(
while read WFY_URL
do
    echo $WFY_URL #Some more action
    COUNTER=$((COUNTER+1))
done
)

echo $COUNTER # output = 0


while 루프를 서브 쉘에 넣을 필요가 없습니다. while 루프 주변의 괄호를 제거하기 만하면 충분합니다. 또는 서브 쉘에 루프를 넣어야하는 경우, 완료 후 카운터를 임시 파일에 한 번 덤프하고이 파일을 서브 쉘 외부로 복원하십시오. 답변으로 최종 절차를 준비하겠습니다.
Znik

답변:


156

첫째, 카운터를 늘리지 않습니다. 변경 COUNTER=$((COUNTER))으로 COUNTER=$((COUNTER + 1))또는 COUNTER=$[COUNTER + 1]그것을 증가 할 것이다.

둘째, 추측대로 서브 쉘 변수를 수신자에게 역 전파하는 것이 더 까다 롭습니다. 서브 쉘의 변수는 서브 쉘 외부에서 사용할 수 없습니다. 이들은 자식 프로세스에 국한된 변수입니다.

이를 해결하는 한 가지 방법은 중간 값을 저장하기 위해 임시 파일을 사용하는 것입니다.

TEMPFILE=/tmp/$$.tmp
echo 0 > $TEMPFILE

# Loop goes here
  # Fetch the value and increase it
  COUNTER=$[$(cat $TEMPFILE) + 1]

  # Store the new value
  echo $COUNTER > $TEMPFILE

# Loop done, script done, delete the file
unlink $TEMPFILE

30
$ [...]는 더 이상 사용되지 않습니다.
chepner

1
@chepner $[...]더 이상 사용되지 않는다는 참조가 있습니까? 대체 솔루션이 있습니까?
blong

9
$[...]POSIX 쉘에서 채택 bash되기 전에 사용되었습니다 $((...)). 공식적으로 더 이상 사용되지 않는지 확실하지 않지만 bashman 페이지 에서 이에 대한 언급을 찾을 수 없으며 이전 버전과의 호환성을 위해서만 지원되는 것으로 보입니다.
chepner

또한, $ (...)를보다 선호한다...
레나 롤랑

7
@blong 다음은 사용 중단을 논의하고 참조하는 $ [...] 대 $ ((...))에 대한 SO 질문입니다. stackoverflow.com/questions/2415724/…
Ogre Psalm33

87
COUNTER=1
while [ Your != "done" ]
do
     echo " $COUNTER "
     COUNTER=$[$COUNTER +1]
done

테스트 된 BASH : Centos, SuSE, RH


1
@kroonwijk 대괄호 앞에 공백이 있어야합니다 ( '단어 구분', 공식적으로 말하면). Bash는 그렇지 않으면 이전 표현식의 끝을 볼 수 없습니다.
EdwardGarson

1
질문이 파이프와 잠시에 대해, 그래서 서브 쉘이 생성되는 경우, 당신의 대답은 잘하지만 당신은 질문에 대답하지 그래서 파이프를 사용하지 마십시오
chrisweb

2
다른 답변에 대한 chepner의 의견에 따라 $[ ]구문은 더 이상 사용되지 않습니다. stackoverflow.com/questions/10515964/…
Mark Haferkamp

이것은 주요 질문을 해결하지 않습니다. 메인 루프는 서브 쉘 아래에 있습니다
Znik

42
COUNTER=$((COUNTER+1)) 

현대 프로그래밍에서 상당히 서투른 구조입니다.

(( COUNTER++ ))

더 "현대적"으로 보입니다. 당신은 또한 사용할 수 있습니다

let COUNTER++

가독성이 향상되었다고 생각한다면. 때때로, Bash는 일을하는 너무 많은 방법을 제공합니다. Perl 철학은 아마도 파이썬이 "올바른 방법은 하나 밖에 없습니다"가 더 적절할 수도 있습니다. 하나가 있다면 그것은 논쟁의 여지가있는 진술입니다! 어쨌든, 나는 목표 (이 경우)가 단순히 변수를 증가시키는 것이 아니라 다른 사람이 이해하고 지원할 수있는 코드를 작성하는 것 (일반 규칙)이라고 제안합니다. 적합성은이를 달성하는 데 큰 도움이됩니다.

HTH


이것은 (하위 프로세스) 루프를 종료 한 후 카운터에서 업데이트 된 값을 얻는 방법 인 원래 질문을 해결하지 않습니다
Luis Vazquez

16

사용해보십시오

COUNTER=$((COUNTER+1))

대신에

COUNTER=$((COUNTER))

8
또는 그냥let "COUNTER++"
nullpotent

2
죄송합니다. 오타였습니다. 실제로 ((COUNTER + 1))
Sparsh Gupta

8
@AaronDigulla : (( COUNTER++ ))(달러 기호 없음)
추후 공지가있을 때까지 일시 중지되었습니다.

2
왜 그런지 모르겠지만 사용할 때 반복적으로 실패 (( COUNTER++ ))하지만 전환했을 때 작동하는 스크립트가 표시 됩니다 COUNTER=$((COUNTER + 1)). GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)
Steven Lu

아마도 해시 뱅 라인이 / bin / bash 대신 / bin / sh로 bash를 실행합니까?
Max

12

이 단일 awk 호출이 grep|grep|awk|awk파이프 라인 과 동일하다고 생각 합니다. 테스트하십시오. 마지막 awk 명령은 아무것도 변경하지 않는 것 같습니다.

COUNTER의 문제점은 while 루프가 서브 쉘에서 실행 중이므로 서브 쉘이 종료 될 때 변수에 대한 변경 사항이 사라진다는 것입니다. 동일한 서브 쉘에서 COUNTER의 값에 액세스해야합니다. 또는 @DennisWilliamson의 조언을 따르고, 프로세스 대체를 사용하고, 서브 쉘을 완전히 피하십시오.

awk '
  /GET \/log_/ && /upstream timed out/ {
    split($0, a, ", ")
    split(a[2] FS a[4] FS $0, b)
    print "http://example.com" b[5] "&ip=" b[2] "&date=" b[7] "&time=" b[8] "&end=1"
  }
' | {
    while read WFY_URL
    do
        echo $WFY_URL #Some more action
        (( COUNTER++ ))
    done
    echo $COUNTER
}

1
감사합니다. 마지막 awk는 기본적으로 end = 1 이후의 모든 항목을 제거하고 끝에 새로운 end = 1을 추가합니다 (다음에 추가되는 모든 항목을 제거 할 수 있도록).
Sparsh Gupta

1
@SparshGupta, 이전 awk는 "end = 1"이후에 아무것도 인쇄하지 않습니다.
glenn jackman

이것은 질문 스크립트를 매우 잘 개선하지만 서브 쉘 내부의 카운터 증가 문제를 해결하지 못합니다
Znik


11

임시 파일을 사용하는 대신 while프로세스 대체를 사용하여 루프 주위에 서브 쉘을 작성하는 것을 피할 수 있습니다 .

while ...
do
   ...
done < <(grep ...)

그건 그렇고, 당신은 모든 것을 grep, grep, awk, awk, awk하나의 awk.

Bash 4.2부터는 다음과 같은 lastpipe옵션이 있습니다.

현재 셸 컨텍스트에서 파이프 라인의 마지막 명령을 실행합니다. lastpipe 옵션은 작업 제어가 활성화 된 경우 효과가 없습니다.

bash -c 'echo foo | while read -r s; do c=3; done; echo "$c"'

bash -c 'shopt -s lastpipe; echo foo | while read -r s; do c=3; done; echo "$c"'
3

프로세스 대체는 루프 내부에서 카운터를 증가시키고 완료되었을 때 외부에서 사용하려는 경우 훌륭합니다. 프로세스 대체의 문제는 파이프를 사용할 때 가능한 실행 된 명령의 상태 코드를 가져올 방법이 없다는 것입니다. $ {PIPESTATUS [*]} 사용
chrisweb

@chrisweb :에 대한 정보를 추가했습니다 lastpipe. 그건 그렇고, 아마도 "${PIPESTATUS[@]}"(별표 대신에) 를 사용해야 할 것입니다 .
추후 공지가있을 때까지 일시 중지되었습니다.

정오표. bash에서 (이전에 실수로 작성한 것처럼 perl이 아님) 종료 코드는 테이블이므로 파이프 체인의 모든 종료 코드를 별도로 확인할 수 있습니다. 먼저 테스트하기 전에이 테이블을 복사해야합니다. 그렇지 않으면 첫 번째 명령 후에 모든 값을 잃게됩니다.
Znik

이것은 내 생각에 많은 보행자에게 변수 값을 저장하기 위해 외부 파일을 사용하지 않고 나를 위해 일한 솔루션입니다.
Luis Vazquez


3

이것이 당신이해야 할 전부입니다 :

$((COUNTER++))

다음 은 bash Shell 학습 , 3rd Edition, pp. 147, 148 에서 발췌 한 것입니다 .

bash 산술 표현식은 Java 및 C 언어의 해당 표현식과 동일합니다. [9] 우선 순위와 연관성은 C에서와 동일합니다. 표 6-2는 지원되는 산술 연산자를 보여줍니다. 이들 중 일부는 특수 문자 (또는 포함)이지만 $ ((...)) 구문 내에 있기 때문에 백 슬래시 이스케이프 할 필요가 없습니다.

..........................

++ 및-연산자는 값을 1 씩 늘리거나 줄일 때 유용합니다. [11] Java 및 C에서와 동일하게 작동합니다. 예를 들어 value ++는 값을 1 씩 증가시킵니다 . 이것을 post-increment 라고합니다 . 도있다 선행 증가 ++ : . 차이점은 예를 통해 분명해집니다.

$ i=0
$ echo $i
0
$ echo $((i++))
0
$ echo $i
1
$ echo $((++i))
2
$ echo $i
2

참조 http://www.safaribooksonline.com/a/learning-the-bash/7572399/를


이것이 제가 필요로하는 버전입니다. 왜냐하면 if진술 조건에서 사용했기 때문입니다 . if [[ $((needsComma++)) -gt 0 ]]; then printf ',\n'; fi 옳든 그르 든, 이것은 안정적으로 작동하는 유일한 버전입니다.
LS

이 양식에서 중요한 것은 한 단계에서 증분을 사용할 수 있다는 것입니다. i=1; while true; do echo $((i++)); sleep .1; done
Bruno Bronosky

1
@LS : if (( needsComma++ > 0 )); then또는if (( needsComma++ )); then
추후 공지가있을 때까지 일시 중지되었습니다.

bash에서 "echo $ ((i ++))"를 사용하면 항상 "/opt/xyz/init.sh : line 29 : i : command not found"메시지가 표시됩니다. 내가 뭘 잘못하고 있는가?
mmo

이것은 루프 외부에서 카운터 값을 얻는 것에 대한 질문을 다루지 않습니다.
Luis Vazquez

1

이것은 간단한 예입니다

COUNTER=1
for i in {1..5}
do   
   echo $COUNTER;
   //echo "Welcome $i times"
   ((COUNTER++));    
done

1
간단한 예이지만 질문에는 적용 할 수 없습니다.
Znik

0

counter스크립트를 업데이트하지 않은 것 같습니다.counter++


오타에 대한 사과, 실제로 작동하지 않는 스크립트에서 ((COUNTER + 1))을 사용하고 있습니다
Sparsh Gupta

value + 1 또는 value ++에 의해 증분되는 것은 상관 없습니다. 서브 쉘이 종료되면 카운터 값이 손실되고이 스크립트에서 시작시 설정된 초기 0 값으로 되돌아갑니다.
Znik

0

식이 ((var++))실패하게 된 두 가지 조건이 있습니다 .

  1. 내가하면 엄격 모드로 떠들썩한 파티를 설정 ( set -euo pipefail) 나는 내 시작하면 제로 증가를 (0).

  2. 일 (1)에서 시작하는 것은 좋지만 엄격 모드에서 0이 아닌 리턴 코드 실패 인 "++"를 평가할 때 증분이 "1"을 리턴합니다.

이 동작을 사용 ((var+=1))하거나 var=$((var+1))피할 수 있습니다.


0

소스 스크립트에 서브 쉘에 문제가 있습니다. 첫 번째 예는 아마도 서브 쉘이 필요하지 않을 것입니다. 그러나 우리는 "Some more action"아래에 무엇이 숨겨져 있는지 모릅니다. 가장 인기있는 대답은 숨겨진 버그로, I / O를 증가시키고, 루프 내부에서 couter를 복원하기 때문에 서브 쉘에서 작동하지 않습니다.

'\'기호를 추가하지 마십시오. bash 인터프리터에게 줄 연속에 대해 알립니다. 나는 그것이 당신이나 누구에게 도움이되기를 바랍니다. 그러나 제 생각에이 스크립트는 AWK 스크립트로 완전히 변환되어야하며 그렇지 않으면 regexp 또는 perl을 사용하여 파이썬으로 다시 작성되어야하지만, 수년에 걸친 perl 인기는 저하됩니다. 파이썬으로 더 잘하십시오.

서브 쉘이없는 수정 된 버전 :

#!/bin/bash
WFY_PATH=/var/log/nginx
WFY_FILE=error.log
COUNTER=0
grep 'GET /log_' $WFY_PATH/$WFY_FILE | grep 'upstream timed out' |\
awk -F ', ' '{print $2,$4,$0}' |\
awk '{print "http://example.com"$5"&ip="$2"&date="$7"&time="$8"&end=1"}' |\
awk -F '&end=1' '{print $1"&end=1"}' |\
#(  #unneeded bracket
while read WFY_URL
do
    echo $WFY_URL #Some more action
    COUNTER=$((COUNTER+1))
done
# ) unneeded bracket

echo $COUNTER # output = 0

정말로 필요한 경우 서브 쉘이있는 버전

#!/bin/bash

TEMPFILE=/tmp/$$.tmp  #I've got it from the most popular answer
WFY_PATH=/var/log/nginx
WFY_FILE=error.log
COUNTER=0
grep 'GET /log_' $WFY_PATH/$WFY_FILE | grep 'upstream timed out' |\
awk -F ', ' '{print $2,$4,$0}' |\
awk '{print "http://example.com"$5"&ip="$2"&date="$7"&time="$8"&end=1"}' |\
awk -F '&end=1' '{print $1"&end=1"}' |\
(
while read WFY_URL
do
    echo $WFY_URL #Some more action
    COUNTER=$((COUNTER+1))
done
echo $COUNTER > $TEMPFILE  #store counter only once, do it after loop, you will save I/O
)

COUNTER=$(cat $TEMPFILE)  #restore counter
unlink $TEMPFILE
echo $COUNTER # output = 0
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.