stdout이 터미널 또는 파이프에 연결되어 있는지 프로그램은 어떻게 알 수 있습니까?


12

segfault 바로 앞의 출력이 필요한 것이므로 segfaulting 프로그램을 디버깅하는 데 문제가 있지만 출력을 파일로 파이핑하면 손실됩니다. 이 답변에 따르면 /unix//a/17339/22615 , 이것은 프로그램의 출력 버퍼가 터미널에 연결되면 즉시 플러시되지만 파이프에 연결되면 특정 지점에서만 플러시되기 때문입니다. 여기 몇 가지 질문이 있습니다.

  • 프로그램은 stdout이 연결된 것을 어떻게 결정합니까?

  • "script"명령은 프로그램이 터미널에 쓸 때와 같은 동작을 어떻게합니까?

  • 스크립트 명령없이이 작업을 수행 할 수 있습니까?


관련 질문은 unix.stackexchange.com/q/513926/5132 입니다.
JdeBP

답변:


23

파일 디스크립터가 터미널 장치를 가리키는 지 여부

프로그램은 isatty()표준 C 함수 를 사용하여 파일 디스크립터가 tty 장치와 연관되어 있는지 알 수 있습니다 (일반적으로 ioctl()fd가 tty 장치를 가리 키지 않을 때 오류가 발생 하는 무해한 tty 특정 시스템 호출 아래에 있음) .

[/의 test유틸리티는 그와 함께 할 수있는 -t운영자입니다.

if [ -t 1 ]; then
  echo stdout is open to a terminal
fi

GNU / Linux 시스템에서 libc 함수 호출 추적 :

$ ltrace [ -t 1 ] | cat
[...]
isatty(1)                                      = 0
[...]

추적 시스템 호출 :

$ strace [ -t 1 ] | cat
[...]
ioctl(1, TCGETS, 0x7fffd9fb3010)        = -1 ENOTTY (Inappropriate ioctl for device)
[...]

파이프를 가리키는 지 알려주기

fd가 pipe / fifo와 연관되어 있는지 판별하기 위해 fstat()시스템 호출을 사용할 수 있습니다.이 호출st_mode 은 해당 fd에서 열린 파일의 유형 및 권한을 필드에 포함 하는 구조를 리턴합니다 . S_ISFIFO()표준 C 매크로 것을 사용할 수있다 st_mode상기 FD는 FIFO / 파이프인지 확인 필드.

을 수행 할 수있는 표준 유틸리티는 fstat()없지만이 stat를 수행 할 수 있는 명령 의 호환되지 않는 구현이 몇 가지 있습니다. zshstat내장과 stat -sf "$fd" +mode첫 문자 (유형 나타내는 문자열 표시 모드로 반환하는 p파이프를). GNU는 stat과 같은 작업을 수행 할 수 있습니다 stat -c %A - <&"$fd", 또한이 stat -c %F - <&"$fd"보고서에 형식을 혼자. BSD 사용 stat: stat -f %St <&"$fd"또는 stat -f %HT <&"$fd".

찾을 수 있는지 말하기

응용 프로그램은 일반적으로 stdout이 파이프인지 상관하지 않습니다. 그들은 검색 가능하도록 신경 쓸지도 모릅니다 (일반적으로 버퍼링할지 여부를 결정하지는 않음).

fd를 검색 할 수 있는지 (파이프, 소켓, tty 장치는 검색 할 수없고, 일반 파일 및 대부분의 블록 장치는 일반적으로 사용 가능한지) 여부를 테스트하기 위해 오프셋이 0 인 상대 lseek()시스템 호출 (무해한)을 시도 할 수 있습니다 . dd인터페이스는 표준 유틸리티 lseek()이지만 lseek()0의 오프셋을 요청하면 구현이 전혀 호출하지 않으므로 해당 테스트에 사용할 수 없습니다 .

zshksh93쉘은 다음 과 같이 탐색 연산자를 내장하고 있습니다.

$ strace -e lseek ksh -c ': 1>#((CUR))' | cat
lseek(1, 0, SEEK_CUR)                   = -1 ESPIPE (Illegal seek)
ksh: 1: not seekable
$ strace -e lseek zsh -c 'zmodload zsh/system; sysseek -w current -u 1 0 || syserror'
lseek(1, 0, SEEK_CUR)                   = -1 ESPIPE (Illegal seek)
Illegal seek

버퍼링 비활성화

script명령은 의사 터미널 쌍을 사용하여 프로그램의 출력을 캡처하므로 프로그램의 stdout (및 stdin 및 stderr)은 의사 터미널 장치가됩니다.

stdout이 터미널 장치에 대한 경우 여전히 버퍼링이 여전히 있지만 라인 기반입니다. printf/ puts및 co는 개행 문자가 출력 될 때까지 아무 것도 쓰지 않습니다. 다른 유형의 파일의 경우 버퍼링은 몇 킬로바이트의 블록 단위입니다.

버퍼링을 비활성화하는 몇 가지 옵션이 있습니다. 여기에서 Q & A ( unbuffer 또는 stdbuf 검색 , 컷 출력을 리디렉션 할 수는 없지만 몇 가지 접근법이 있습니다)에서 socat/ script/ expect/ 로 수행 할 수있는 의사 터미널 을 사용하여 버퍼링을 비활성화 수 있습니다 unbuffer(AN expect스크립트) / zshzpty또는 GNU의 FreeBSD와의 수행으로 버퍼링을 비활성화하려면 실행 파일에 코드를 주입하여 stdbuf.


1
멋진 답변입니다. 대단히 감사합니다!
mowwwalker

또 다른 Linux 고유 접근 방식은 /proc디렉토리 를 탐색 하고 각 /proc/<integer>/디렉토리 에 대해 serverfault.com/q/48330/363611/proc/<integer>/fd/ 에서 동일한 inode 번호를 가진 파일 디스크립터를 찾아서 찾는 것입니다. 그러나 이는 설명 된 syscall을 사용할 수없는 경우에만 스크립트에 유용합니다. Stephane의 답변에 있으며 적절한 솔루션보다 더 많은 해결 방법이 있습니다. IMHOpipefs
Sergiy Kolodyazhnyy

BSD에서는 lseek터미널 및 기타 문자 장치에서 성공하고 각 성공적인 read ()에서 증가하는 카운터를 재설정 / 설정하기 만하면됩니다. 이것이 그들이 "찾을 수 있는지"모르겠습니다.
mosvy

@mowwwalker이 답변으로 문제가 해결 되었다면 잠시 후 왼쪽의 확인 표시를 클릭하여 동의하십시오 . 그러면 질문이 답변 된 것으로 표시되며 Stack Exchange 사이트에 감사가 표현되는 방식입니다.
디저트
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.