비결정적 출력이 있는 파이프

비결정적 출력이 있는 파이프

쉘에서 여러 번 빠르게 연속해서 실행 하더라도 때로는 작동하고 때로는 작동하지 않는 명령을 우연히 발견했습니다 bash(다른 쉘에서는 동작을 테스트하지 않았습니다). 문제는 BEGIN파이프 끝에 있는 명령문 블록의 변수를 읽는 것으로 국한되었습니다 . awk일부 실행 중에는 변수가 BEGIN블록에서 올바르게 읽히지만 다른 실행 중에는 작업이 실패합니다. 이 비정상적인 동작이 다른 사람에 의해 재현될 수 있다고 가정하면(내 시스템의 문제로 인한 결과는 아님) 그 불일치를 설명할 수 있습니까?

다음 파일을 입력으로 사용하십시오 tmp.

cat > tmp <<EOF
a   a
b   *
aa  a
aaa a
aa  a
a   a
c   *
aaa a
aaaa    a
d   *
aaa a
a   a
aaaaa   a
e   *
aaaa    a
aaa a
f   *
aa  a
a   a
g   *
EOF

내 시스템에서는 파이프

 awk '{if($2!~/\*/) print $1}' tmp | tee >(wc -l | awk '{print $1}' > n.txt) | sort | uniq -c | sort -k 1,1nr | awk 'BEGIN{getline n < "n.txt"}{print $1 "\t" $1/n*100 "\t" $2}'

올바른 출력이 생성됩니다.

4   28.5714 a
4   28.5714 aaa
3   21.4286 aa
2   14.2857 aaaa
1   7.14286 aaaaa

또는 오류 메시지:

awk: cmd. line:1: (FILENAME=- FNR=1) fatal: division by zero attempted

어떻게 명령을 내릴 수 있나요?가능한난수 생성이 포함되지 않고 그 사이에 환경이 변경되지 않은 경우 연속으로 두 번 실행하면 다른 출력이 제공됩니까?

이 동작이 얼마나 터무니없는지 보여주기 위해 위의 파이프라인을 10번 연속 실행하여 생성된 출력을 살펴보겠습니다.

for x in {1..10}; do echo "Iteration ${x}"; awk '{if($2!~/\*/) print $1}' tmp | tee >(wc -l | awk '{print $1}' > n.txt) | sort | uniq -c | sort -k 1,1nr | awk 'BEGIN{getline n < "n.txt"}{print $1 "\t" $1/n*100 "\t" $2}'; done
Iteration 1
awk: cmd. line:1: (FILENAME=- FNR=1) fatal: division by zero attempted
Iteration 2
4   28.5714 a
4   28.5714 aaa
3   21.4286 aa
2   14.2857 aaaa
1   7.14286 aaaaa
Iteration 3
4   28.5714 a
4   28.5714 aaa
3   21.4286 aa
2   14.2857 aaaa
1   7.14286 aaaaa
Iteration 4
awk: cmd. line:1: (FILENAME=- FNR=1) fatal: division by zero attempted
Iteration 5
awk: cmd. line:1: (FILENAME=- FNR=1) fatal: division by zero attempted
Iteration 6
awk: cmd. line:1: (FILENAME=- FNR=1) fatal: division by zero attempted
Iteration 7
4   28.5714 a
4   28.5714 aaa
3   21.4286 aa
2   14.2857 aaaa
1   7.14286 aaaaa
Iteration 8
awk: cmd. line:1: (FILENAME=- FNR=1) fatal: division by zero attempted
Iteration 9
4   28.5714 a
4   28.5714 aaa
3   21.4286 aa
2   14.2857 aaaa
1   7.14286 aaaaa
Iteration 10
awk: cmd. line:1: (FILENAME=- FNR=1) fatal: division by zero attempted

close참고: 파일이 열려 있는 것과 관련된 문제인 경우를 대비하여 변수를 읽은 후 파일( awk )을 닫으려고 했습니다 . 그러나 일관성 없는 출력이 여전히 존재합니다.

답변1

리디렉션에 경쟁 조건이 있습니다. 이것:

>(wc -l | awk '{print $1}' > n.txt)

병렬로 실행:

awk 'BEGIN{getline n < "n.txt"}...'

나중에 파이프라인에서. 때로는 프로그램이 실행되기 시작할 때 n.txt여전히 비어 있습니다.awk

이는 Bash 참조 매뉴얼에 (간접적으로) 문서화되어 있습니다. 안에관로:

파이프라인의 각 명령 출력은 다음 명령의 입력으로 파이프됩니다. 즉, 각 명령은 이전 명령의 출력을 읽습니다.이 연결은 명령에 의해 지정된 리디렉션 전에 실행됩니다..

그런 다음:

파이프라인의 각 명령은 자체 하위 셸에서 실행됩니다.

(강조 추가).모두파이프의 프로세스는 이전 프로그램이 완료되거나 작업 수행을 시작할 때까지 기다리지 않고 입력과 출력이 함께 연결된 상태로 시작됩니다. 그것 앞에,프로세스 교체그리고 >(...)는:

매개변수 및 변수 확장, 명령 대체, 산술 확장을 동시에 수행합니다.

이는 명령을 실행하는 하위 프로세스가 일찍 시작되고 그 전에 wc -l | awk ...리디렉션이 지워지지만 오류를 일으키는 프로세스는 곧 시작됨을 의미합니다 . 두 명령 모두 병렬로 실행됩니다. 동시에 여러 프로세스가 실행됩니다.n.txtawk

출력이 기록되기 전에 명령 블록을 awk실행 BEGIN하면 오류가 발생합니다.wcn.txt. 이 경우 n변수는 비어 있으므로 숫자로 사용하면 0이 됩니다. BEGIN파일을 작성한 후 실행 하면 모든 것이 잘 작동합니다.

이런 일이 발생하는 경우는 운영 체제 스케줄러와 어떤 프로세스가 먼저 슬롯을 가져오는지에 따라 달라지며 이는 사용자 관점에서 볼 때 본질적으로 무작위입니다. 최종 버전이 awk일찍 실행 되거나 wc파이프라인이 조금 늦게 예약되면 awk작업 실행을 시작할 때 파일이 여전히 비어 있고 모든 것이 중단됩니다. 어떤 코어가 경합 지점에 먼저 도달하는지에 따라 이러한 프로세스가 실제로 동시에 다른 코어에서 실행될 가능성이 높습니다. 당신이 얻게 될 효과는 명령이 자주 작동하지만 때때로 당신이 게시한 오류로 인해 실패한다는 것입니다.


일반적으로 파이프는 파이프인 경우에만 안전합니다. stdout에서 stdin까지 괜찮지만 프로세스가 병렬로 실행되기 때문에주문을 위해 다른 통신 채널에 의존하는 것은 신뢰할 수 없습니다., 파일과 같은 프로세스 또는 한 프로세스의 일부는 표준 입력을 읽어 함께 잠기지 않는 한 다른 프로세스의 일부 이전이나 이후에 실행됩니다.

여기서 해결 방법은 필요하기 전에 모든 파일 쓰기를 완료하는 것입니다. 줄 끝에서 다음 명령이 실행되기 전에 전체 파이프와 모든 리디렉션이 완료되었는지 확인하십시오. 이 명령은 결코 신뢰할 수 없지만 이 구조에서 실제로 작동해야 하는 경우 최종 명령을 실행하기 전에 비어 있지 않을 sleep때까지 지연( ) 또는 루프를 삽입하여 원하는 방식으로 작동할 가능성을 높일 수 있습니다.n.txtawk

답변2

pipe의 표현식은 process substitution경쟁 조건을 유발 bash하지만 ksh, zsh는 그렇지 않습니다.

여기서 가장 중요한 문제는 zsh기다리는 것이 bash아니라 기다리는 것입니다.

자세한 내용을 보실 수 있습니다여기.

빠른 수정 사항을 추가하여 sleep 1항상 사용할 수 awk있도록 하세요 .n.txt

awk 'BEGIN{system("sleep 1");getline n < "n.txt"};{print $1 "\t" $1/n*100 "\t" $2}'

답변3

경쟁 조건이 설정되었습니다. 그러나 wc레코드를 개별적으로 계산할 필요가 없는 더 간단한 솔루션을 원한다면 awk다음을 수행할 수 있습니다.

awk '{if($2!~/\*/){print $1;++n}END{print n >"n.txt"}' tmp | sort | uniq -c ...

그 외에도 값이 메모리에 맞는 한 awk계산할 수 있으며 x/n 계산도 수행할 수 있지만 sort|uniq -cmatch/action을 사용하여 "무작위" 순서로 출력하는 것도 더 깔끔합니다.

awk '$2!~/\*/{++k[$1];++n} END{for(i in k){print k[i]"\t"k[i]/n*100"\t"i}}' tmp | sort -k1nr

아니면 최근에암소 비슷한 일종의 영양 awk올바른 순서를 사용하고 사용하지 않도록 PROCINFO["sorted_in"]="@ind_num_desc"설정할 수 있습니다 .forsort

관련 정보