일부 프로세스를 실행한다고 가정 해보십시오.
#!/usr/bin/env bash
foo &
bar &
baz &
wait;
위의 스크립트를 다음과 같이 실행합니다.
foobarbaz | cat
내가 알 수있는 한, 프로세스 중 하나가 stdout / stderr에 쓰면 출력이 인터리브되지 않습니다. stdio의 각 줄은 원자 적 인 것처럼 보입니다. 어떻게 작동합니까? 각 라인의 원 자성을 제어하는 유틸리티는 무엇입니까?
일부 프로세스를 실행한다고 가정 해보십시오.
#!/usr/bin/env bash
foo &
bar &
baz &
wait;
위의 스크립트를 다음과 같이 실행합니다.
foobarbaz | cat
내가 알 수있는 한, 프로세스 중 하나가 stdout / stderr에 쓰면 출력이 인터리브되지 않습니다. stdio의 각 줄은 원자 적 인 것처럼 보입니다. 어떻게 작동합니까? 각 라인의 원 자성을 제어하는 유틸리티는 무엇입니까?
답변:
그들은 인터리브를한다! 분리되지 않은 상태로 짧은 출력 버스트 만 시도했지만 실제로는 특정 출력이 분리되지 않도록 보장하기가 어렵습니다.
프로그램 이 출력을 버퍼링 하는 방법에 따라 다릅니다 . 표준 입출력 라이브러리 들이있는 거 쓰기가 사용하는 버퍼 출력을보다 효율적으로 할 때 대부분의 프로그램이 사용하는. 프로그램이 파일에 쓰기 위해 라이브러리 함수를 호출하자마자 데이터를 출력하는 대신이 함수는이 데이터를 버퍼에 저장하고 버퍼가 채워지면 실제로 데이터를 출력합니다. 이는 출력이 일괄 적으로 수행됨을 의미합니다. 보다 정확하게는 세 가지 출력 모드가 있습니다.
프로그램은 각 파일을 다시 프로그래밍하여 다르게 작동하고 명시 적으로 버퍼를 플러시 할 수 있습니다. 프로그램이 파일을 닫거나 정상적으로 종료되면 버퍼가 자동으로 플러시됩니다.
동일한 파이프에 쓰는 모든 프로그램이 라인 버퍼링 모드를 사용하거나 버퍼되지 않은 모드를 사용하고 출력 함수를 한 번만 호출하여 각 라인을 작성하고 라인이 단일 청크로 쓸 수있을 정도로 짧은 경우 출력은 전체 라인의 인터리빙이됩니다. 그러나 프로그램 중 하나가 완전 버퍼 모드를 사용하거나 회선이 너무 길면 혼합 된 회선이 표시됩니다.
다음은 두 프로그램의 출력을 인터리브하는 예입니다. Linux에서 GNU coreutils를 사용했습니다. 이러한 유틸리티의 다른 버전은 다르게 작동 할 수 있습니다.
yes aaaa
aaaa
본질적으로 라인 버퍼 모드와 동등한 것으로 영원히 씁니다 . 이 yes
유틸리티는 실제로 한 번에 여러 줄을 쓰지만 출력이 나올 때마다 출력은 전체 줄 수입니다.echo bbbb; done | grep b
bbbb
완전히 버퍼링 된 모드에서 영원히 씁니다 . 8192의 버퍼 크기를 사용하며 각 줄의 길이는 5 바이트입니다. 5는 8192를 나누지 않으므로 쓰기 간의 경계는 일반적으로 선 경계에 없습니다.함께 던지자.
$ { yes aaaa & while true; do echo bbbb; done | grep b & } | head -n 999999 | grep -e ab -e ba
bbaaaa
bbbbaaaa
baaaa
bbbaaaa
bbaaaa
bbbaaaa
ab
bbbbaaa
보시다시피, 때때로 grep이 중단되고 그 반대도 마찬가지입니다. 약 0.001 %의 회선 만 중단되었지만 발생했습니다. 출력은 무작위로 설정되어 중단 횟수가 달라 지지만 매번 최소 몇 번의 중단이 발생했습니다. 버퍼 당 라인 수가 줄어듦에 따라 인터럽트 가능성이 높아 지므로 라인이 더 길면 인터럽트 라인의 비율이 높아집니다.
출력 버퍼링 을 조정 하는 방법에는 여러 가지가 있습니다 . 주요 내용은 다음과 같습니다.
stdbuf -o0
GNU coreutils 및 FreeBSD와 같은 일부 다른 시스템에서 기본 설정을 변경하지 않고 stdio 라이브러리를 사용하는 프로그램에서 버퍼링을 해제하십시오 . 또는을 사용하여 라인 버퍼링으로 전환 할 수 있습니다 stdbuf -oL
.unbuffer
. 일부 프로그램은 다른 방식으로 다르게 작동 할 수 있습니다. 예를 들어 grep
출력이 터미널 인 경우 기본적으로 색상을 사용합니다.--line-buffered
GNU grep 에 전달하여 프로그램을 구성하십시오 .이번에는 양쪽에서 라인 버퍼링을 사용하여 위의 스 니펫을 다시 살펴 보겠습니다.
{ stdbuf -oL yes aaaa & while true; do echo bbbb; done | grep --line-buffered b & } | head -n 999999 | grep -e ab -e ba
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
그래서 이번에는 그래프를 방해하지 않았지만, 그래프는 때때로 그래버 렸습니다. 나는 왜 나중에 올 것이다.
각 프로그램이 한 번에 한 줄씩 출력하고 라인이 충분히 짧으면 출력 라인이 깔끔하게 분리됩니다. 그러나 이것이 작동하는 데 걸리는 시간에는 제한이 있습니다. 파이프 자체에는 전송 버퍼가 있습니다. 프로그램이 파이프로 출력 할 때, 데이터는 기록기 프로그램에서 파이프의 전송 버퍼로 복사 된 후 나중에 파이프의 전송 버퍼에서 리더 프로그램으로 복사됩니다. (적어도 개념적으로는-커널이 때때로 이것을 단일 복사본으로 최적화 할 수 있습니다.)
파이프의 전송 버퍼에 맞는 것보다 복사 할 데이터가 더 많은 경우 커널은 한 번에 하나의 버퍼를 복사합니다. 여러 프로그램이 같은 파이프에 쓰고 커널이 선택한 첫 번째 프로그램이 하나 이상의 버퍼를 쓰려고한다면 커널이 두 번째로 동일한 프로그램을 다시 선택할 것이라는 보장은 없습니다. 예를 들어, P 가 버퍼 크기이고 foo
2 * P 바이트 bar
를 쓰려고하고 3 바이트를 쓰려고 할 경우 가능한 인터리빙은에서 P 바이트,에서 foo
3 바이트 bar
,에서 P 바이트입니다 foo
.
내 시스템에서 위의 yes + grep 예제 yes aaaa
로 돌아 가면 한 번에 8192 바이트 버퍼에 들어갈 수있는 한 많은 행을 씁니다. 쓸 5 바이트 (4 개의 인쇄 가능한 문자와 줄 바꿈)가 있으므로 매번 8190 바이트를 씁니다. 파이프 버퍼 크기는 4096 바이트입니다. 따라서 yes에서 4096 바이트를 가져온 다음 grep에서 일부 출력을 가져온 다음 나머지 쓰기는 yes (8190-4096 = 4094 바이트)에서 얻을 수 있습니다. 4096 바이트는 819 라인과 aaaa
고독한 공간을 남겨 둡니다 a
. 따라서이 론이있는 행 a
다음에 grep에서 한 번의 쓰기를 수행하여을 표시합니다 abbbb
.
무슨 일이 일어나고 있는지 자세히 알고 싶다면 getconf PIPE_BUF .
시스템의 파이프 버퍼 크기를 알려주고 각 프로그램이 수행 한 전체 시스템 호출 목록을 볼 수 있습니다
strace -s9999 -f -o line_buffered.strace sh -c '{ stdbuf -oL yes aaaa & while true; do echo bbbb; done | grep --line-buffered b & }' | head -n 999999 | grep -e ab -e ba
라인 길이가 파이프 버퍼 크기보다 작은 경우 라인 버퍼링은 출력에 혼합 된 라인이 없도록 보장합니다.
줄 길이가 더 길면 여러 프로그램이 동일한 파이프에 쓸 때 임의의 혼합을 피할 수있는 방법이 없습니다. 분리를 보장하려면 각 프로그램을 다른 파이프에 쓰도록하고 프로그램을 사용하여 선을 결합해야합니다. 예를 들어 GNU Parallel 은 기본적으로이 작업을 수행합니다.
cat
고양이 프로세스가 foo / bar / baz에서 전체 라인을 수신하지만 다른 라인에서 반 라인이 아닌 다른 라인에서 라인을 수신 하도록 모든 라인이 원자로 작성되도록하는 좋은 방법은 무엇입니까? bash 스크립트로 할 수있는 일이 있습니까?
awk
동일한 ID에 대해 두 줄 (또는 그 이상)의 출력을 생성 find -type f -name 'myfiles*' -print0 | xargs -0 awk '{ seen[$1]= seen[$1] $2} END { for(x in seen) print x, seen[x] }'
했지만 find -type f -name 'myfiles*' -print0 | xargs -0 cat| awk '{ seen[$1]= seen[$1] $2} END { for(x in seen) print x, seen[x] }'
모든 ID에 대해 한 줄만 올바르게 생성 한 경우에도 적용됩니다.
http://mywiki.wooledge.org/BashPitfalls#Non-atomic_writes_with_xargs_-P 는 이것을 살펴 보았습니다.
GNU xargs는 여러 작업을 동시에 실행할 수 있습니다. -P n 여기서 n은 병렬로 실행할 작업 수입니다.
seq 100 | xargs -n1 -P10 echo "$a" | grep 5 seq 100 | xargs -n1 -P10 echo "$a" > myoutput.txt
이것은 많은 상황에서 잘 작동하지만 기만적 인 결함이 있습니다. 혼합됩니다.
$ perl -e 'print "a"x2000, "\n"' > foo $ strace -e write bash -c 'read -r foo < foo; echo "$foo"' >/dev/null write(1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 1008) = 1008 write(1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 993) = 993 +++ exited with 0 +++
echo 또는 printf를 여러 번 호출하면 분명히 동일한 문제가 발생합니다.
slowprint() { printf 'Start-%s ' "$1" sleep "$1" printf '%s-End\n' "$1" } export -f slowprint seq 10 | xargs -n1 -I {} -P4 bash -c "slowprint {}" # Compare to no parallelization seq 10 | xargs -n1 -I {} bash -c "slowprint {}" # Be sure to see the warnings in the next Pitfall!
각 작업은 두 개 이상의 개별 write () 호출로 구성되므로 병렬 작업의 출력이 함께 혼합됩니다.
출력을 혼합 해제해야하는 경우 출력이 직렬화되도록 보장하는 도구 (예 : GNU Parallel)를 사용하는 것이 좋습니다.
xargs echo
echo bash 내장을 호출하지 않지만 echo
from 유틸리티를 호출합니다 $PATH
. 어쨌든 나는 bash 4.4로 bash echo 동작을 재현 할 수 없습니다. Linux에서 4K보다 큰 파이프 (/ dev / null 아님)에 대한 쓰기는 원 자성이 보장되지 않습니다.