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-cmdstdout은 라인 버퍼링되지만 s는 완전히 버퍼링됩니다. 즉, 대화형 사용자는 출력이 사용 가능해지자마자 (변환을 통해) cmd출력을 볼 수 없음을 의미합니다 . 그러나 지연되고 볼륨이 높습니다.cmdother-cmd

unbuffer cmd | other-cmd

cmd표준 출력이 파이프로 들어가더라도 라인 기반 버퍼링을 복원하므로 유용합니다 .

이를 위해 cmd의사 터미널에서 시작하여 해당 의사 터미널의 콘텐츠를 파이프로 전달합니다. 따라서 cmd사용자와 다시 대화하고 라인 버퍼링을 수행한다고 생각하십시오.

unbuffer실제로는 에 기록되어 있습니다 expect.expect샘플 스크립트 소스 코드, 일반적으로 expect운영 체제에서 제공하는 소프트웨어 패키지에 포함되어 있습니다.

expect는 의사 터미널을 사용하여 터미널 애플리케이션과 자동화된 상호 작용을 수행하는 도구이므로 이 unbuffer명령은 작성하기 쉽습니다 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

이는 오류가 stderr unbuffer ls /x 2> /dev/null로 전송되지 않고 stdout이 병합되는 이유를 설명합니다./dev/null

이제 unbuffer자체 표준 입력에서 아무것도 읽히지 않으며 자체 표준 입력으로 전송되는 것도 없습니다 cmd.

A | unbuffer cmd | B이는 작동하지 않는다는 것을 의미합니다 .

이것이 -p( pipe용) 옵션이 들어오는 곳입니다. 코드에 표시된 대로 with는 다른 채널의 데이터를 처리하는 활성 루프로 대신 사용 -p됩니다 .unbufferinteractexpect

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

뿐만 아니라 다음을 사용할 수 있습니다 interact.expect

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

A | unbuffer -p cmd | B의 출력을 A입력으로 읽을 수 있기 때문에 괜찮지만 cmd다음을 의미합니다.

  • unbuffer내부 의사 터미널을 구성하는 데 사용되지만 모드에서는 set stty_init "-echo"사용되지 않습니다 . raw특히 isig(( ) // 처리), (흐름 제어, / ( ))는 비활성화되지 않습니다. 입력이 터미널 장치인 경우(대신 s가 사용되는 방식) 괜찮습니다. 호스트 장치가 모드에 있기 때문입니다. 이는 두 터미널이 모두 비활성화되어 있다는 점을 제외하고 처리가 호스트 터미널에서 내장된 의사 터미널로 전송됨을 의미합니다. 그래서 당신은 당신이 입력하는 내용을 볼 수 없습니다. 그러나 터미널 장치가 아닌 경우 입력(출력이 처리될 때)의 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.

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

    본 라인 에디터는 라인 크기에 제한이 있으므로 편집이 가능합니다(내 시스템의 4095(Linux),tty 속도의 1/5FreeBSD의 경우 1), 다음과 같은 결과가 됩니다:언버퍼링하면 모든 문자가 링으로 변환되나요?: 멍청한 애플리케이션(예 cat: . Linux에서는 4094번째 이후의 모든 문자가 무시되지만 허용되고 행이 제출됩니다. FreeBSD에서는 38400/5 문자 이후의 추가 문자가 거부되고(심지어 ) BEL 결과가 터미널²로 전송됩니다. 이것이 2321 BEL(10001 - 38400/5)을 얻는 이유를 설명합니다.\n\n

  • 의사 터미널 장치에 대한 EOF 처리가 까다롭습니다. stdin에서 EOF 를 발견하면 해당 unbuffer정보를 cmd. 대신, 그 시점에 모든 것이 해체되고 종료됩니다(맨 페이지에는 이 제한 사항이 언급되어 있습니다).seq 10 | od -vtcseqodod

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

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

우리는 실제로 다음과 같은 방법으로 이 문제를 해결할 수 있습니다.

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

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

그 다음에:

$ 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

expect설치 (TCL 인터프리터 필요)는 의사 터미널을 통한 표준 출력만 필요한 경우 과잉처럼 보입니다.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

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

쉘에는 zshpseudo-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새 세션의 메소드를 제외하고( ctty및 옵션을 사용하지 않는 한) 이들 모두는 결국 새 터미널에서 실행됩니다 . setid따라서 이제 이러한 "고정"은 unbuffer호스트 터미널 세션의 백그라운드에서 시작된 경우 cmd호스트 터미널에서 데이터 읽기를 중지하지 않습니다 . 예를 들어, unbuffer cat&백그라운드 작업은 결국 터미널에서 읽게 되어 혼란을 야기합니다.


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

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

관련 정보