파이프에서 버퍼링 끄기


395

두 가지 명령을 호출하는 스크립트가 있습니다.

long_running_command | print_progress

long_running_command인쇄가 진행하지만 난 그게 불만입니다. 나는 print_progress그것을 더 멋지게 만들기 위해 사용 하고 있습니다 (즉, 진행 상황을 한 줄로 인쇄합니다).

문제 : stdout에 파이프를 연결하면 4K 버퍼가 활성화되어 멋진 인쇄 프로그램에 아무것도 얻지 못합니다 ... 아무것도 ... 아무것도 ... 많은 ... :)

4K 버퍼를 비활성화하려면 어떻게 long_running_command해야합니까 (아니요, 소스가 없습니다)?


1
따라서 파이핑없이 long_running_command를 실행하면 진행률 업데이트가 올바르게 표시되지만 파이핑시 버퍼링됩니까?

1
그렇습니다.
Aaron Digulla

20
버퍼링을 제어하는 ​​간단한 방법에 대한 무능력은 수십 년 동안 문제가되어왔다. 예를 들면 다음과 같습니다. marc.info/?l=glibc-bug&m=98313957306297&w=4 기본적으로 "이 작업을 수행 할 수 없으며 내 입장을 정당화하기위한 박수 함정입니다"


1
실제로 충분한 데이터를 기다리는 동안 지연을 일으키는 것은 파이프가 아닌 stdio입니다. 파이프에는 용량이 있지만 파이프에 데이터가 기록 되 자마자 다른 쪽 끝에서 즉시 읽을 수 있습니다.
Sam Watkins

답변:


254

패키지의 unbuffer일부로 제공되는 명령을 사용할 수 있습니다.expect

unbuffer long_running_command | print_progress

unbufferlong_running_commandpseudoterminal (pty) 을 통해 연결 하여 시스템이 인터랙티브 프로세스로 취급하므로 지연의 원인이 될 수있는 파이프 라인에서 4-kiB 버퍼링을 사용하지 않습니다.

더 긴 파이프 라인의 경우 각 명령의 버퍼를 해제해야 할 수 있습니다 (최종 명령 제외).

unbuffer x | unbuffer -p y | z

3
실제로, 대화식 프로세스에 연결하기 위해 pty를 사용하는 것은 일반적으로 예상되는 사실입니다.

15
unbuffer를 파이프 라이닝 할 때 unbuffer가 stdin에서 읽도록 -p 인수를 사용해야합니다.

26
참고 : 데비안 시스템에서이라고 expect_unbuffer하고,에 expect-dev, 패키지가 아닌 expect패키지
bdonlan

4
@ bdonlan : 적어도 우분투 (데비안 기반)에서 expect-dev둘 다 제공 unbuffer하며 expect_unbuffer(전자는 후자에 대한 심볼릭 링크입니다). 링크는 expect 5.44.1.14-1(2009) 부터 제공 됩니다.
jfs

1
참고 : Ubuntu 14.04.x ​​시스템에서는 expect-dev 패키지에도 있습니다.
Alexandre Mazel

462

이 고양이를 스키닝하는 또 다른 방법 stdbuf은 GNU Coreutils의 일부인 프로그램 을 사용 하는 것입니다 (FreeBSD에는 자체 프로그램이 있습니다).

stdbuf -i0 -o0 -e0 command

입력, 출력 및 오류에 대한 버퍼링이 완전히 해제됩니다. 일부 응용 프로그램의 경우 성능상의 이유로 라인 버퍼링이 더 적합 할 수 있습니다.

stdbuf -oL -eL command

동적으로 연결된 응용 프로그램의 stdio버퍼링 ( printf(), fputs()...)에 대해서만 작동하며 , 해당 응용 프로그램이 표준 스트림 자체의 버퍼링을 자체적으로 조정하지 않는 경우에만 작동하지만 대부분의 응용 프로그램에 적용되어야합니다.


6
"unbuffer"는 패키지 안에있는 Ubuntu에 설치해야합니다 : expect-dev 2MB ...
lepe

2
이것은 버퍼링을 풀기위한 기본 raspbian 설치에서 잘 작동합니다. 그래도 sudo stdbuff … command작품을 찾았습니다 stdbuff … sudo command.
natevw

20
@qdii stdbuf는에서 설정 한 기본값을 덮어 쓰기 tee때문에 작동하지 않습니다 . 의 매뉴얼 페이지를 참조하십시오 . teestdbufstdbuf
11

5
@lepe 기괴하게도 unbuffer는 x11과 tcl / tk에 의존합니다. 즉, 서버가없는 서버에 설치하는 경우 실제로 80MB 이상이 필요합니다.
jpatokal

10
@qdii stdbufLD_PRELOAD메커니즘을 사용 하여 동적으로로드 된 자체 라이브러리를 삽입합니다 libstdbuf.so. 이는 표준 libc를 사용하지 않고 setuid 또는 파일 기능이 설정되어 있고 정적으로 링크되어있는 이러한 종류의 실행 파일에서는 작동하지 않음을 의미합니다. 이 경우 unbuffer/ script/ 와 함께 솔루션을 사용하는 것이 좋습니다 socat. setuid / capabilities가있는 stdbuf 도 참조하십시오 .
pabouk

75

에 대한 라인 버퍼링 출력 모드를 설정하는 또 다른 방법 은 의사 터미널 (pty)에서 실행 long_running_command되는 script명령 을 사용하는 것 long_running_command입니다.

script -q /dev/null long_running_command | print_progress      # FreeBSD, Mac OS X
script -c "long_running_command" /dev/null | print_progress    # Linux

15
이후 +1 멋진 트릭은, script같은 오래된 명령은, 모든 유닉스 플랫폼에서 사용할 수 있습니다.
Aaron Digulla

5
-q리눅스 에서도 필요 합니다 :script -q -c 'long_running_command' /dev/null | print_progress
jfs

1
적어도 대화 형 터미널에서 시작할 때 백그라운드 에서 스크립트 stdin를 실행하는 것이 불가능한 스크립트를 읽는 것처럼 보입니다 long_running_command. 해결 방법으로을 사용하지 않기 /dev/null때문에 stdin을에서 리디렉션 할 수있었습니다 . long_running_commandstdin
haridsv 2016

1
안드로이드에서도 작동합니다.
not2qubit

3
한 가지 중요한 단점 : ctrl-z가 더 이상 작동하지 않습니다 (예 : 스크립트를 일시 중단 할 수 없음). 예를 들어 echo | sudo 스크립트 -c / usr / local / bin / ec2-snapshot-all / dev / null | 프로그램과 상호 작용할 수없는 경우 ts입니다.
rlpowell

66

위해 grep, sed그리고 awk당신은 출력 라인 버퍼링을 강제 할 수 있습니다. 당신이 사용할 수있는:

grep --line-buffered

출력을 라인 버퍼링합니다. 기본적으로 출력은 표준 출력이 터미널 일 때 라인 버퍼링되고 다른 방식으로 블록 버퍼링됩니다.

sed -u

출력 라인을 버퍼링하십시오.

자세한 내용은이 페이지를 참조하십시오 : http://www.perkin.org.uk/posts/how-to-fix-stdio-buffering.html


51

출력이 터미널로 이동하지 않을 때 libc가 버퍼링 / 플러싱을 수정하는 데 문제가있는 경우 socat 을 시도해야합니다 . 거의 모든 종류의 I / O 메커니즘간에 양방향 스트림을 생성 할 수 있습니다. 그중 하나는 의사 tty에게 말하는 포크 ​​프로그램입니다.

 socat EXEC:long_running_command,pty,ctty STDIO 

그것이하는 일은

  • 의사 tty를 만들다
  • pty의 슬레이브 쪽이 stdin / stdout 인 fork long_running_command
  • pty의 마스터 측과 두 번째 주소 사이에 양방향 스트림을 설정하십시오 (여기서는 STDIO입니다)

이것이와 동일한 출력을 제공하면 long_running_command파이프를 계속 사용할 수 있습니다.

편집 : 와우 ​​언 버퍼 답변을 보지 못했습니다! 음, socat은 어쨌든 훌륭한 도구입니다.


1
... 나는 socat에 대해 몰랐습니다. netcat과 비슷해 보였을 것입니다. ;) 감사합니다.

3
socat -u exec:long_running_command,pty,end-close -여기에 사용 하겠습니다
Stéphane Chazelas

20

당신이 사용할 수있는

long_running_command 1>&2 |& print_progress

문제는 libc가 stdout을 화면에 올릴 때 라인 버퍼가되고 stdout이 파일을 볼 때 풀 버퍼가된다는 것입니다. 그러나 stderr에는 버퍼가 없습니다.

파이프 버퍼에 문제가 있다고 생각하지 않습니다 .libc의 버퍼 정책에 관한 것입니다.


네가 옳아; 내 질문은 여전히 ​​: 재 컴파일하지 않고 libc의 버퍼 정책에 어떻게 영향을 줄 수 있습니까?
Aaron Digulla

@ StéphaneChazelas의 FD1은 표준 에러로 리디렉션합니다
왕 HongQin

@ StéphaneChazelas 나는 당신의 논쟁 포인트를 얻지 못합니다. PLZ 테스트를 수행, 작동합니다
Wang HongQin

3
OK, 무슨 일이 것은 모두이다 zsh(어디 |&에서 CSH에서 적응 온다)와 bash, 당신이 할 때 cmd1 >&2 |& cmd2, 두 전략 중 1과 2는 외부 표준 출력에 연결되어 있습니다. 따라서 외부 stdout이 터미널 일 때 버퍼링을 방지하는 데 효과적이지만 출력이 파이프를 통과하지 않기 때문에 ( print_progress아무것도 인쇄하지 않음). 따라서 long_running_command & print_progressprint_progress stdin이 기록기가없는 파이프라는 점을 제외 하면 동일 합니다. 과 ls -l /proc/self/fd >&2 |& cat비교하여 확인할 수 있습니다 ls -l /proc/self/fd |& cat.
Stéphane Chazelas

3
문자 그대로 |&는 짧기 때문 입니다 2>&1 |. 그래서 cmd1 |& cmd2입니다 cmd1 1>&2 2>&1 | cmd2. 따라서 fd 1과 2는 모두 원래 stderr에 연결되며 파이프에 아무것도 기록되지 않습니다. ( s/outer stdout/outer stderr/g이전 의견에서).
Stéphane Chazelas

11

예전에는 표준 출력이 터미널에 쓰여질 때 기본적으로 라인 버퍼링됩니다-줄 바꿈이 쓰여질 때 줄이 터미널에 쓰여집니다. 표준 출력이 파이프로 전송되면 완전히 버퍼링되므로 표준 I / O 버퍼가 채워질 때 파이프 라인의 다음 프로세스로만 데이터가 전송됩니다.

그것이 문제의 원인입니다. 파이프에 쓰는 프로그램을 수정하지 않고 고칠 수있는 일이 많이 있는지 잘 모르겠습니다. 플래그 setvbuf()와 함께 함수를 사용하여 _IOLBF무조건 stdout라인 버퍼 모드로 전환 할 수 있습니다. 그러나 나는 그것을 프로그램에 적용하는 쉬운 방법을 보지 못한다. 또는 프로그램이 fflush()적절한 지점에서 (각 출력 행 후) 수행 할 수 있지만 동일한 주석이 적용됩니다.

파이프를 의사 터미널로 교체하면 표준 I / O 라이브러리는 출력이 터미널 (터미널 유형이기 때문에)이라고 생각하고 버퍼를 자동으로 라인 처리한다고 가정합니다. 그러나 그것은 사물을 다루는 복잡한 방법입니다.


7

나는 이것이 오래된 질문이며 이미 많은 답변을 받았지만 버퍼 문제를 피하려면 다음과 같이 시도하십시오.

stdbuf -oL tail -f /var/log/messages | tee -a /home/your_user_here/logs.txt

이것은 실시간으로 로그를 출력하고 logs.txt파일 로 저장 하며 버퍼는 더 이상 tail -f명령에 영향을 미치지 않습니다 .


4
이것은 두 번째 답변처럼 보입니다 :-/
Aaron Digulla

2
stdbuf는 gnu coreutils에 포함되어 있습니다 (최신 버전 8.25에서 확인되었습니다). 이것은 임베디드 리눅스에서 작동한다는 것을 확인했습니다.
zhaorufei

stdbuf의 문서에서 NOTE: If COMMAND adjusts the buffering of its standard streams ('tee' does for example) then that will override corresponding changes by 'stdbuf'.
shrewmouse

6

파이프에 문제가 있다고 생각하지 않습니다. 장기 실행 프로세스가 자체 버퍼를 자주 플러시하지 않는 것 같습니다. 파이프의 버퍼 크기를 변경하는 것은 그것을 해결하기위한 해킹이지만 커널을 다시 빌드하지 않고는 가능하지 않다고 생각합니다.


18
기본 원인은 stdout이 tty가 아닌 경우 libc가 4k 버퍼링으로 전환되기 때문입니다.
Aaron Digulla

5
매우 흥미 롭습니다! 파이프는 버퍼링을 일으키지 않기 때문입니다. 버퍼링을 제공하지만 파이프에서 읽을 경우 사용 가능한 데이터가 있으면 파이프에서 버퍼를 기다릴 필요가 없습니다. 따라서 범인은 응용 프로그램에서 stdio 버퍼링이 될 것입니다.

3

이 게시물 에 따르면 파이프 ulimit를 하나의 단일 512 바이트 블록으로 줄일 수 있습니다. 확실히 버퍼링을 끄지는 않지만 512 바이트는 4K보다 작습니다.


3

chad의 답변 과 비슷한 맥락에서 다음과 같은 작은 스크립트를 작성할 수 있습니다.

# save as ~/bin/scriptee, or so
script -q /dev/null sh -c 'exec cat > /dev/null'

그런 다음이 scriptee명령을 대신 사용하십시오 tee.

my-long-running-command | scriptee

아아, 리눅스에서 완벽하게 작동하는 것과 같은 버전을 얻을 수없는 것처럼 보이므로 BSD 스타일 유닉스로 제한 된 것 같습니다.

Linux에서 이것은 가깝지만 완료되면 프롬프트를 다시 얻지 못합니다 (Enter 키를 누를 때까지).

script -q -c 'cat > /proc/self/fd/1' /dev/null

왜 작동합니까? "스크립트"는 버퍼링을 해제합니까?
Aaron Digulla

@Aaron Digulla : script터미널을 에뮬레이트하므로 예, 버퍼링을 해제한다고 믿습니다. 또한 전송 된 각 문자를 다시 에코 하므로 예제에서 cat전송됩니다 /dev/null. 내부에서 실행되는 프로그램 script에 관한 한 대화식 세션과 대화 중입니다. 나는 expect이와 관련하여 비슷하다고 생각 하지만 script기본 시스템의 일부 일 가능성이 큽니다.
jwd

내가 사용하는 이유 tee는 스트림 사본을 파일로 보내는 것입니다. 파일은 어디에 지정 scriptee됩니까?
브루노 브로 노 스키

@BrunoBronosky : 맞습니다.이 프로그램의 이름이 잘못되었습니다. 실제로 '티'작업을 수행하지 않습니다. 원래 질문에 따라 출력 버퍼링을 비활성화합니다. 아마도 "scriptcat"이라고 불리어야 할 수도 있습니다 (연결을 수행하지는 않지만 ...). 어쨌든 cat명령을로 바꿀 tee myfile.txt수 있으며 원하는 효과를 얻을 수 있습니다.
jwd

2

이 영리한 해결책을 찾았습니다. (echo -e "cmd 1\ncmd 2" && cat) | ./shell_executable

이것은 트릭을 수행합니다. cat추가 입력을 읽고 (EOF까지) echo인수를 입력 스트림에 넣은 후에 파이프에 전달합니다 shell_executable.


2
실제로, ; cat의 출력을 보지 못합니다 echo. 서브 쉘에서 두 개의 명령을 실행하면 둘의 출력이 파이프로 전송됩니다. 서브 쉘 ( 'cat')의 두 번째 명령은 부모 / 외부 표준 입력에서 읽으므로 작동합니다.
Aaron Digulla

0

에 따르면 파이프 버퍼 크기는 커널에 설정 될 것으로 보인다 및 변경 커널을 다시 컴파일 할 필요합니다.


7
나는 그것이 다른 버퍼라고 생각합니다.
Samuel Edwin Ward
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.