stdout / stderr가 인터리빙하는 것을 방지하는 것은 무엇입니까?


13

일부 프로세스를 실행한다고 가정 해보십시오.

#!/usr/bin/env bash

foo &
bar &
baz &

wait;

위의 스크립트를 다음과 같이 실행합니다.

foobarbaz | cat

내가 알 수있는 한, 프로세스 중 하나가 stdout / stderr에 쓰면 출력이 인터리브되지 않습니다. stdio의 각 줄은 원자 적 인 것처럼 보입니다. 어떻게 작동합니까? 각 라인의 원 자성을 제어하는 ​​유틸리티는 무엇입니까?


3
명령이 얼마나 많은 데이터를 출력합니까? 몇 킬로바이트를 출력하도록하십시오.
Kusalananda

명령 중 하나가 개행 전에 몇 kb를 출력하는 것을 의미합니까?
Alexander Mills

아니요, 다음과 같습니다 : unix.stackexchange.com/a/452762/70524
muru

답변:


22

그들은 인터리브를한다! 분리되지 않은 상태로 짧은 출력 버스트 만 시도했지만 실제로는 특정 출력이 분리되지 않도록 보장하기가 어렵습니다.

출력 버퍼링

프로그램 이 출력을 버퍼링 하는 방법에 따라 다릅니다 . 표준 입출력 라이브러리 들이있는 거 쓰기가 사용하는 버퍼 출력을보다 효율적으로 할 때 대부분의 프로그램이 사용하는. 프로그램이 파일에 쓰기 위해 라이브러리 함수를 호출하자마자 데이터를 출력하는 대신이 함수는이 데이터를 버퍼에 저장하고 버퍼가 채워지면 실제로 데이터를 출력합니다. 이는 출력이 일괄 적으로 수행됨을 의미합니다. 보다 정확하게는 세 가지 출력 모드가 있습니다.

  • 버퍼링되지 않음 : 버퍼를 사용하지 않고 데이터가 즉시 기록됩니다. 프로그램이 출력을 작은 문자 (예 : 문자 별)로 쓰면 느려질 수 있습니다. 표준 오류의 기본 모드입니다.
  • 완전 버퍼링 : 버퍼가 가득 찼을 때만 데이터가 기록됩니다. stderr를 제외하고 파이프 나 일반 파일에 쓸 때의 기본 모드입니다.
  • 라인 버퍼링 : 데이터는 각 개행 후에 또는 버퍼가 가득 찼을 때 기록됩니다. stderr를 제외하고 터미널에 쓸 때의 기본 모드입니다.

프로그램은 각 파일을 다시 프로그래밍하여 다르게 작동하고 명시 적으로 버퍼를 플러시 할 수 있습니다. 프로그램이 파일을 닫거나 정상적으로 종료되면 버퍼가 자동으로 플러시됩니다.

동일한 파이프에 쓰는 모든 프로그램이 라인 버퍼링 모드를 사용하거나 버퍼되지 않은 모드를 사용하고 출력 함수를 한 번만 호출하여 각 라인을 작성하고 라인이 단일 청크로 쓸 수있을 정도로 짧은 경우 출력은 전체 라인의 인터리빙이됩니다. 그러나 프로그램 중 하나가 완전 버퍼 모드를 사용하거나 회선이 너무 길면 혼합 된 회선이 표시됩니다.

다음은 두 프로그램의 출력을 인터리브하는 예입니다. Linux에서 GNU coreutils를 사용했습니다. 이러한 유틸리티의 다른 버전은 다르게 작동 할 수 있습니다.

  • yes aaaaaaaa본질적으로 라인 버퍼 모드와 동등한 것으로 영원히 씁니다 . 이 yes유틸리티는 실제로 한 번에 여러 줄을 쓰지만 출력이 나올 때마다 출력은 전체 줄 수입니다.
  • echo bbbb; done | grep bbbbb완전히 버퍼링 된 모드에서 영원히 씁니다 . 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 -o0GNU coreutils 및 FreeBSD와 같은 일부 다른 시스템에서 기본 설정을 변경하지 않고 stdio 라이브러리를 사용하는 프로그램에서 버퍼링을 해제하십시오 . 또는을 사용하여 라인 버퍼링으로 전환 할 수 있습니다 stdbuf -oL.
  • 이 용도로만 만들어진 단자를 통해 프로그램의 출력을 지시하여 라인 버퍼링으로 전환하십시오 unbuffer. 일부 프로그램은 다른 방식으로 다르게 작동 할 수 있습니다. 예를 들어 grep출력이 터미널 인 경우 기본적으로 색상을 사용합니다.
  • 예를 들어 --line-bufferedGNU 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 가 버퍼 크기이고 foo2 * P 바이트 bar를 쓰려고하고 3 바이트를 쓰려고 할 경우 가능한 인터리빙은에서 P 바이트,에서 foo3 바이트 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 스크립트로 할 수있는 일이 있습니까?
Alexander Mills

1
이것은 내가 수백 개의 파일을 가지고 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에 대해 한 줄만 올바르게 생성 한 경우에도 적용됩니다.
αғsнιη

인터리빙을 방지하기 위해 Node.js와 같은 프로그래밍 환경에서 bash / shell을 사용하여 그렇게 할 수 있습니다.
Alexander Mills

1
@JoL 파이프 버퍼가 가득 차서 발생합니다. 나는 이야기의 두 번째 부분을 써야한다는 것을 알고 있었다. .. 완료.
Gilles 'SO- 악마 그만해

1
@OlegzandrDenman TLDR 추가 : 인터리브를 수행합니다. 그 이유는 복잡합니다.
Gilles 'SO- 악한 중지'

1

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 echoecho bash 내장을 호출하지 않지만 echofrom 유틸리티를 호출합니다 $PATH. 어쨌든 나는 bash 4.4로 bash echo 동작을 재현 할 수 없습니다. Linux에서 4K보다 큰 파이프 (/ dev / null 아님)에 대한 쓰기는 원 자성이 보장되지 않습니다.
Stéphane Chazelas
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.