unbuffer -p가 입력을 망가뜨리는 이유는 무엇입니까?

unbuffer -p가 입력을 망가뜨리는 이유는 무엇입니까?
$ seq 10 | unbuffer -p od -vtc
0000000   1  \n   2  \n   3  \n   4  \n   5  \n   6  \n   7  \n   8  \n

어디로 9가고 어디로 10갔습니까?

$ printf '\r' | unbuffer -p od -An -w1 -vtc
  \n

\r로 변경된 이유는 무엇입니까 \n?

$ : | unbuffer -p printf '\n' | od -An -w1 -vtc
  \r
  \n
$ unbuffer -p printf '\n' | od -An -w1 -vtc
  \r
      \n

뭐야?

$ printf foo | unbuffer -p cat
$

왜 출력이 없나요(및 1초 지연)?

$ printf '\1\2\3foo bar\n'  | unbuffer -p od -An -w1 -vtc
$

왜 출력이 없나요?

$ (printf '\23'; seq 10000) | unbuffer -p cat

출력이 없는데 왜 멈추나요?

$ unbuffer -p sleep 10

내가 입력한 내용을 볼 수 없는 이유는 무엇입니까( sleep읽지 않았음에도 삭제되는 이유는 무엇입니까)?

또한, 또한:

$ echo test | unbuffer -p grep foo && echo found foo
found foo

grep찾았 foo지만 그것을 포함하는 줄을 인쇄하지 않았습니까?

$ unbuffer -p ls /x 2> /dev/null
ls: cannot access '/x': No such file or directory

오류가 왜 발생하지 않았습니까 /dev/null?

또한보십시오모든 문자를 벨로 변환하는 버퍼를 해제하시겠습니까?

$ echo ${(l[10000][foo])} | unbuffer -p cat | wc -c
4095

그 내용은 다음과 같습니다.

$ lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description:    Debian GNU/Linux trixie/sid
Release:        n/a
Codename:       trixie
$ uname -rsm
Linux 6.5.0-3-amd64 x86_64
$ expect -c 'puts "expect [package require Expect] tcl [info patchlevel]"'
expect 5.45.4 tcl 8.6.13
$ /proc/self/exe --version
zsh 5.9 (x86_64-debian-linux-gnu)

Ubuntu 22.04 또는 FreeBSD 12.4-RELEASE-p5에서도 동일합니다( od여기서 명령을 조정해야 하며 위의 4095 대신 2321(모든 BEL 문자)을 얻습니다).

답변1

unbuffer출력이 터미널 장치로 이동하지 않을 때 일부 명령이 수행하는 버퍼링을 비활성화하는 도구입니다.

출력이 터미널 장치로 전달되면 명령은 출력을 적극적으로 보고 있는 실제 사용자가 있다고 가정하므로 사용 가능한 즉시 출력을 보냅니다. 음, 정확히는 아니지만 라인 기반으로 보냅니다. 즉, 출력할 준비가 되자마자 완성된 라인을 보냅니다.

stdout이 일반 파일이나 파이프인 경우와 같이 터미널 장치로 이동하지 않는 경우 최적화를 위해 블록으로 보냅니다. 이는 의 수가 적다는 것을 의미 write()하고 파이프의 경우 다른 쪽 끝에 있는 판독기가 자주 깨어날 필요가 없다는 것을 의미하며 이는 더 적은 컨텍스트 전환을 의미합니다.

그러나 이는 다음을 의미합니다.

cmd | other-cmd

other-cmd일종의 필터링/변환 명령이 있는 터미널에서 실행하면 other-cmd의 stdout은 라인 버퍼링되지만 의 s는 전체 버퍼링됩니다. 이는 대화형 사용자가 의 출력을 즉시 cmd볼 수 없음을 의미합니다 cmd( 에 의해 변환됨 ). other-cmd사용할 수 있지만 지연되고 대량으로 배치됩니다.

unbuffer cmd | other-cmd

cmd표준 출력이 파이프로 이동하더라도 라인 기반 버퍼링을 복원하므로 도움이 됩니다 .

이를 위해 cmd의사 터미널에서 시작하여 해당 의사 터미널에서 나오는 내용을 파이프로 전달합니다. 그래서 cmd사용자와 다시 대화하고 있다고 생각하고 라인 버퍼링을 수행합니다.

unbuffer실제로 에 기록되어 있습니다 expect. 그것은expect소스 코드 의 예제 스크립트, 종종 expectOS에서 제공하는 패키지에 포함됩니다.

expectunbuffer의사 터미널을 사용하여 터미널 응용 프로그램과 자동 상호 작용을 수행하는 데 사용되는 도구이므로 expect. 농담으로,버그unbuffer의 매뉴얼 페이지 섹션에는 다음이 있습니다.매뉴얼 페이지는 프로그램보다 깁니다.그리고 실제로,프로그램그것은 단지:

#!/bin/sh
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh8.6 "$0" ${1+"$@"}

package require Expect


# -*- tcl -*-
# Description: unbuffer stdout of a program
# Author: Don Libes, NIST

if {[string compare [lindex $argv 0] "-p"] == 0} {
    # pipeline
    set stty_init "-echo"
    eval [list spawn -noecho] [lrange $argv 1 end]
    close_on_eof -i $user_spawn_id 0
    interact {
        eof {
            # flush remaining output from child
            expect -timeout 1 -re .+
            return
        }
    }
} else {
    set stty_init "-opost"
    set timeout -1
    eval [list spawn -noecho] $argv
    expect
    exit [lindex [wait] 3]
}

맨 페이지에서 확인하고 볼 수 있듯이 옵션 unbuffer도 지원합니다 -p.

에서 unbuffer cmd의사 터미널은 cmd의 stdout에 연결될 뿐만 아니라 stdin 및 stderr에도 연결됩니다( expect명령과 상호 작용하기 위한 도구임을 기억하십시오).

$ tty; unbuffer readlink /proc/self/fd/{0..2}
/dev/pts/14
/dev/pts/15
/dev/pts/15
/dev/pts/15

이는 unbuffer ls /x 2> /dev/null오류를 에 보내지 않은 이유를 설명하며 /dev/nullstderr은 stdout과 병합됩니다.

이제 unbuffer자체 stdin에서 아무 것도 읽지 않으며 cmd.

즉, A | unbuffer cmd | B작동하지 않습니다.

이것이 -p(for pipe) 옵션이 들어오는 곳입니다. 코드에서 볼 수 있듯이 with 는 -p대신 다른 채널에서 들어오는 데이터를 처리하는 활성 루프로 unbuffer사용됩니다 .interactexpect

expect명령문만 사용하면 expect(프로그램/TCL 라이브러리) 의사 터미널에서 나오는 내용( cmd예를 들어 stdout 또는 stderr를 통해 슬레이브 측에 쓰는 내용)을 읽고 자체 stdout으로 보냅니다.

를 사용하면 interact다음 expect과 같은 기능도 수행됩니다.

  • 자체 표준 입력에서 읽은 내용을 의사 터미널로 보냅니다(그래서 cmd거기에서 읽을 수 있음).
  • 또한 unbuffer의 stdin이 터미널 장치인 경우 로컬이 비활성화된 모드 interact로 전환합니다 .rawecho

에서 의 출력을 입력으로 읽을 수 있다는 A | unbuffer -p cmd | B점 에서는 좋지만 다음과 같은 몇 가지 의미가 있습니다.Acmd

  • unbuffer로 내부 의사 터미널을 구성 set stty_init "-echo"하지만 raw모드에서는 구성하지 않습니다. 특히 (( ) / / isig의 처리 ), (흐름 제어, / ( ))는 비활성화되지 않습니다. 입력이 터미널 장치인 경우(이것은 사용하기 위한 방식이지만 그렇지 않음 ) 호스트 장치가 모드에 있으므로 처리가 호스트 터미널에서 내장된 의사 장치로 이동한다는 의미이므로 괜찮습니다. 터미널 은 둘 다 비활성화되어 있어 입력한 내용을 볼 수 없다는 점만 제외하면 됩니다 . 그러나 터미널 장치가 아닌 경우 이는 예를 들어 입력의 0x3 바이트( )가( 출력을 처리할 때와 같이 ) SIGINT를 트리거하고 명령을 종료하고 0x19 바이트( )가 흐름을 중지한다는 것을 의미합니다. 비활성화되지 않은 것은 's가 's로 변경된 이유를 설명합니다 .^C\3^Z^\ixon^Q^S\23expectinteractunbufferrawecho^Cprintf '\3'printf '\23'icrnl\r\n

  • stty -opost가 없으면 수행되는 작업을 수행하지 않습니다 -p. 이는 \n의 출력이 로 cmd변경된 이유를 설명합니다 \r\n. 그리고 입력이 터미널 장치인 경우 해당 장치를 에 넣는다는 사실이 비활성화 raw되어 opost에서 출력되는 개행 문자가 로 od변환되지 않을 때 맹글링된 터미널 출력을 설명합니다 \r\n.

  • 내부 의사 터미널에는 여전히 행 편집기가 활성화되어 있으므로 입력에서 오는 문자가 없으면 아무 것도 전송되지 않습니다. 이는 왜 아무것도 cmd인쇄 하지 않는지 설명합니다.\r\nprintf foo | unbuffer -p cat

    그리고 해당 줄 편집기에는 줄 크기에 대한 제한이 있으므로 편집할 수 있습니다(내 시스템의 4095(Linux),tty 속도의 5분의 1¹ FreeBSD의 경우) 다음과 같은 종류의 문제가 발생합니다.모든 문자를 벨로 변환하는 버퍼를 해제하시겠습니까?: 와 같은 멍청한 애플리케이션에서 키보드로 너무 긴 줄을 입력하려고 할 때와 같은 일이 일어납니다 cat. Linux에서는 4094 번째 문자 이후의 모든 문자가 무시되지만 \n허용되고 해당 행을 제출합니다. FreeBSD에서는 38400/5자를 입력한 후 추가 항목이 거부되고(심지어 \n) BEL이 터미널²로 전송됩니다. 2321 BEL(10001 - 38400/5)을 얻는 이유를 설명합니다.

  • 의사 터미널 장치에서는 EOF 처리가 까다롭습니다. 의 stdin 에 EOF가 표시되면 unbuffer해당 정보를 에 전달할 수 없습니다 cmd. 따라서 에서는 종료된 seq 10 | od -vtc후에도 결코 오지 않을 의사 터미널로부터 더 많은 입력을 기다리고 있습니다. 대신, 그 시점에서 모든 것이 해체되고 종료됩니다(맨 페이지에는 해당 제한 사항이 언급되어 있습니다).seqodod

unbuffer자체 목적을 위해 내장된 pseudo-tty를 raw -echo모드로 설정하고 호스트 터미널 장치(있는 경우)를 그대로 두는 것이 훨씬 더 좋습니다 . 그러나 expect해당 작동 모드를 실제로 지원하지 않으며 해당 모드를 위해 설계되지 않았습니다.

이제 unbufferstdout 버퍼링 해제에 관한 것이라면 stdin 및 stderr을 건드려야 할 이유가 없습니다.

실제로 다음을 수행하여 이 문제를 해결할 수 있습니다.

unbuffer() {
  command unbuffer sh -c 4<&0 5>&2 '
    exec <&4 4<&- 2>&5 5>&- "$@"' sh "$@"
}

이는 sh원래 stdin 및 stderr(fds 4 및 5를 통해 호출 쉘에 의해 전달됨; expect내부적으로 명시적으로 사용하는 것처럼 fd 3을 사용하지 않음)을 복원하는 데 사용됩니다.

그 다음에:

$ echo test | unbuffer readlink /proc/self/fd/{0..2} 2> /dev/null | cat
pipe:[184479]
/dev/pts/16
/dev/null

stdout만이 버퍼링되지 않은 의사 터미널로 이동합니다.

그리고 다른 모든 문제는 사라집니다.

$ unbuffer ls /x 2> /dev/null
$ printf '\r'  | unbuffer od -An -w1 -vtc
  \r
$ : | unbuffer printf '\n' | od -An -w1 -vtc
  \n
$ unbuffer printf '\n' | od -An -w1 -vtc
  \n
$ printf foo | unbuffer cat
foo
$ printf '\1\2\3foo bar\n' | unbuffer od -An -w1 -vtc
 001
 002
 003
   f
   o
   o

   b
   a
   r
  \n
$ (printf '\23'; seq 10000) | unbuffer cat -vte | head
^S1$
2$
3$
4$
5$
6$
7$
8$
9$
10$
$ unbuffer sleep 10
I see what I type
$ I see what I type
zsh: command not found: I
$ echo test | unbuffer grep foo || echo not found
not found
$ echo ${(l[10000][foo])} | unbuffer cat | wc -c
10001

또한 설치 (TCL 인터프리터가 필요함)는 stdout이 의사 터미널을 통과하도록 expect만드는 것뿐이라면 약간 과잉인 것 같습니다 .cmd

socat또한 그것을 할 수 있습니다:

$ echo test | socat -u system:'readlink /proc/self/fd/[0-2]; wc -c',pty,raw - 2> /dev/null | cat
pipe:[187759]
/dev/pts/17
/dev/null
5

(실패 종료 상태를 기록하지만 그렇지 않으면 명령의 종료 상태를 전파하지 않습니다).

zsh에는 pseudo-tty에 대한 지원도 내장되어 있으며 unbuffer다음을 사용하면 거의 노력하지 않고도 함수를 작성할 수 있습니다.

zmodload zsh/zpty
zmodload zsh/zselect
unbuffer() {
  {
    return "$(
      exec 6>&1 >&5 5>&-
      # here fds go:
      #  0,3: orig stdin
      #    1: orig stdout
      #  2,4: orig stderr
      #    5: closed
      #    6: to return argument
      zpty -b unbuffer '
        stty raw
        exec <&3 3<&- 2>&4 4>&-
        # here fds go:
        #     0: orig stdin
        #     1: pseudo unbuffering tty
        #     2: orig stderr
        # 3,4,5: closed
        #     6: to return argument
        "$@" 6>&-
        echo "$?" >&6 
      '
      fd=$REPLY
      until
        zselect -r $fd
        zpty -r unbuffer
        (( $? == 2 ))
      do
        continue
      done
    )"
  } 3<&0 4>&2 5>&1
}

새 세션에서 접근 방식( 및 옵션을 socat사용하지 않는 한 ) 을 제외하고 모든 항목이 새 터미널에서 실행된다는 점에 주의하세요 . 따라서 이제 해당 "고정된" 항목이 호스트 터미널 세션의 백그라운드에서 시작 되면 호스트 터미널에서 읽는 것이 중단되지 않습니다. 예를 들어 터미널에서 백그라운드 작업을 읽는 것으로 끝나서 혼란을 야기할 수 있습니다.cttysetidunbuffercmdunbuffer cat&


¹ 65536으로 제한됩니다.속도의사 터미널은 관련이 없지만 광고가 하나 있어야 하며 이것을 테스트한 FreeBSD 시스템에서는 기본적으로 38400입니다. 속도는 터미널을 제어하는 ​​속도에서 복사되므로 해당 버퍼를 확대하기 위해 호출하기 전에 (최대값 AFAICT) expect을 수행할 수 있습니다 . 그러나 여전히 전체 10000자 큰 줄을 얻지 못할 수도 있습니다. 그건stty speed 115200unbuffer드라이버 코드에 설명되어 있음. 첫 번째 호출에서 요청한 unbuffer -p cat만큼만 반환 하고 tty 드라이버는 입력 라인에서 많은 양을 반환했기 때문에 4096바이트만 반환 됩니다 .catread()하지만 나머지는 버렸어(!). 로 바꾸면 unbuffer -p dd bs=65536전체 줄(최대 115200/5바이트)을 얻을 수 있습니다.

² 스크립트 에서 set stty_init "-echo"로 바꾸면 이러한 BEL을 피할 수 있지만 데이터를 얻는 데는 도움이 되지 않습니다.set stty_init "-echo -imaxbel"unbuffer

관련 정보