쉘에서 여러 번 빠르게 연속해서 실행 하더라도 때로는 작동하고 때로는 작동하지 않는 명령을 우연히 발견했습니다 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.txt
awk
출력이 기록되기 전에 명령 블록을 awk
실행 BEGIN
하면 오류가 발생합니다.wc
n.txt
. 이 경우 n
변수는 비어 있으므로 숫자로 사용하면 0이 됩니다. BEGIN
파일을 작성한 후 실행 하면 모든 것이 잘 작동합니다.
이런 일이 발생하는 경우는 운영 체제 스케줄러와 어떤 프로세스가 먼저 슬롯을 가져오는지에 따라 달라지며 이는 사용자 관점에서 볼 때 본질적으로 무작위입니다. 최종 버전이 awk
일찍 실행 되거나 wc
파이프라인이 조금 늦게 예약되면 awk
작업 실행을 시작할 때 파일이 여전히 비어 있고 모든 것이 중단됩니다. 어떤 코어가 경합 지점에 먼저 도달하는지에 따라 이러한 프로세스가 실제로 동시에 다른 코어에서 실행될 가능성이 높습니다. 당신이 얻게 될 효과는 명령이 자주 작동하지만 때때로 당신이 게시한 오류로 인해 실패한다는 것입니다.
일반적으로 파이프는 파이프인 경우에만 안전합니다. stdout에서 stdin까지 괜찮지만 프로세스가 병렬로 실행되기 때문에주문을 위해 다른 통신 채널에 의존하는 것은 신뢰할 수 없습니다., 파일과 같은 프로세스 또는 한 프로세스의 일부는 표준 입력을 읽어 함께 잠기지 않는 한 다른 프로세스의 일부 이전이나 이후에 실행됩니다.
여기서 해결 방법은 필요하기 전에 모든 파일 쓰기를 완료하는 것입니다. 줄 끝에서 다음 명령이 실행되기 전에 전체 파이프와 모든 리디렉션이 완료되었는지 확인하십시오. 이 명령은 결코 신뢰할 수 없지만 이 구조에서 실제로 작동해야 하는 경우 최종 명령을 실행하기 전에 비어 있지 않을 sleep
때까지 지연( ) 또는 루프를 삽입하여 원하는 방식으로 작동할 가능성을 높일 수 있습니다.n.txt
awk
답변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 -c
match/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"
설정할 수 있습니다 .for
sort