수도관이 손상되었는지 어떻게 알 수 있나요?

수도관이 손상되었는지 어떻게 알 수 있나요?

1표준 출력이 파이프로 리디렉션되는 POSIX 쉘 스크립트가 있습니다 . 스크립트를 실행하는 동안 어느 시점에서 파이프가 끊어질 수 있으며, 이것이 언제 발생하는지 (내 쉘 스크립트에서) 알고 싶습니다.

그래서 나는 이것을 시도했습니다 :

(
  trap "" PIPE  # prevent shell from terminating due to SIGPIPE
  while :; do
    echo trying to write to stdout >&2
    echo writing something to stdout || break
    echo successfully written to stdout >&2
    sleep 1
  done
  echo continuing here after loop >&2
) | sleep 3

다음을 인쇄합니다.

trying to write to stdout
successfully written to stdout
trying to write to stdout
successfully written to stdout
trying to write to stdout
successfully written to stdout
trying to write to stdout
sh: 5: echo: echo: I/O error
continuing here after loop

이 예에서는 sleep스크립트를 사용하여 프로그램의 표준 출력을 대체합니다. 3초 후에 sleep종료되고 파이프가 끊어집니다.

우리는 stdout만 파이핑하므로 sleepstderr을 사용하여 그 사이에 일부 디버그 메시지를 처리할 수 있습니다.

규정에 따르면 손상된 파이프에 쓰면 SIGPIPE가 발생하며 기본 동작은 프로그램을 종료하는 것입니다.POSIXsignal.h. 그렇기 때문에 우리는 trap신호를 받아들이고 무시해야 합니다.

종료 시 sleep파이프가 중단되고 이어서 echo writing something to stdoutSIGPIPE가 발생하여 포착(무시)되어 echo실패하고 || break루프를 종료합니다. 스크립트는 문제 없이 계속 실행됩니다.

그래서 위의 예는 잘 작동합니다. 명백한 주요 단점은 파이프라인이 여전히 작동하는지 확인하기 위해 "stdout에 쓰기"를 많이 사용하여 파이프라인에 스팸을 보낸다는 것입니다. "쓰기" 파이프 echo writing something to stdout로 교체하면 printf ""파이프가 손상된 지 오래되었음에도 불구하고 SIGPIPE가 발생하지 않고 루프가 계속됩니다.

어떡해?

답변1

스크립트를 실행하는 동안 어느 시점에서 파이프가 끊어질 것이며 언제 이런 일이 발생하는지 알고 싶습니다.

파이프에 쓰려고 할 때만 이를 알 수 있습니다.

write()Linux 매뉴얼 페이지에 따르면, 판독기 없이 파이프의 쓰기 측에 대한 모든 호출은 0바이트가 기록되더라도 신호/오류를 제공해야 하는 것 같습니다 . 하지만 제가 시도하고 있는 쉘은 인쇄할 것이 없으면 전체 시스템 호출을 건너뛰므로 도움이 되지 않습니다.

0이 아닌 양의 데이터를 쓰는 경우, 쓰는 동안 어느 시점에서 스크립트가 차단되는 것을 발견할 수 있습니다. 즉, 판독기가 작업 완료를 무시하고 파이프 버퍼가 가득 차는 경우입니다.

그리고 또 댓글에서 이렇게 말씀하셨습니다.

기본적으로 쉘 스크립트에서 선택/폴링을 사용하고 싶다고 생각합니다.

...이 경우 실제로 쉘에서 올바른 프로그래밍 언어로 전환해야 합니다. 아니면 zselect프런트엔드로 사용할 수 있는 모듈이 있는 Zsh로 전환하세요 select().https://zsh.sourceforge.io/Doc/Release/Zsh-Modules.html#The-zsh_002fzselect-Module

select()파이프의 읽기 끝이 닫힐 때 찾는 데 도움이 되지 않을 것이라고 확신합니다 .

답변2

적어도 Linux 및 FreeBSD에서는 poll()마스크를 사용하여 POLLERR끊어진 파이프를 감지할 수 있습니다 .

poll()POSIX 도구 상자에는 CLI 인터페이스가 없지만 일반적으로 사용 가능한 인터페이스를 사용할 수 있습니다 ( , 또는 perl등의 많은 POSIX 유틸리티와 반대 ).paxbcm4

perl -MIO::Poll -e '$p=IO::Poll->new; $p->mask(STDOUT,POLLERR); $p->poll'

표준 출력의 파이프가 끊어지면 반환됩니다.

SSH 클라이언트가 종료될 때 원격 명령을 종료하는 사용 사례:

ssh host '
  exec perl -MIO::Poll -we '\''
    $SIG{CHLD} = sub{wait; exit($? & 127 ? 128|($?&127) : $?>>8)};
    exec "sleep 3600 # example" unless fork;
    $p = IO::Poll->new;
    $p->mask(STDOUT, POLLERR);
    $p->poll;
    kill "HUP", 0'\'

/proc/$pid/fd/$fdLinux에서는 누군가 읽기 또는 읽기+쓰기 모드로 파이프를 열면 중단 없이 파이프가 손상될 수 있습니다 . 여기서 는 $fd쓰기 모드로 파이프를 연 프로세스의 fd입니다.$pid

$ exec 3> >(:)
$ perl -MIO::Poll -e '$p=IO::Poll->new; $p->mask(STDOUT,POLLERR); $p->poll'  >&3 && echo broken
broken
$ exec 4< /dev/fd/3
$ echo unbroken >&3
$ cat <&4
unbroken

내 생각에는 상황을 조사하기보다는 그것을 받아들이고 대처하는 것이 더 낫다고 생각합니다.

쉘의 경우 다음이 printf내장되어 있습니다.

(
  trap 'echo>&2 Pipe is broken' PIPE
  while printf 'Whatever\n'; do
    sleep 1
  done
) | sleep 5

SIGPIPE가 처리됩니다. printf내장되어 있지 않으면 이를 실행하는 프로세스가 SIGPIPE로 인해 종료됩니다 . 종료 상태를 기준으로 확인할 수 있습니다 [ "$(kill -l "$?") = PIPE ].

예를 들어 SIGPIPE를 무시하면 trap '' PIPE손상된 파이프에 쓸 때 프로세스(하위 프로세스 포함)가 SIGPIPE를 수신하지 못하지만 write()여전히 실패합니다 EPIPE(오류는 일반적으로 프로세스를 종료하여 처리됩니다).

편집하다

@TheDiveO가 지적했듯이이 비슷한 질문, Linux select()(및 FreeBSD도 마찬가지)는 열린 fd(쓰기 전용 모드에서도)가 손상된 경우(감시된 fd 목록에 있는 경우) 파이프에 반환합니다.읽다.

$ zmodload zsh/zselect
$ (zselect -r 1; echo>&2 done) | sleep 1
done

따라서 로그인 셸이 zsh인 시스템으로 sshing하는 경우 다음을 수행할 수 있습니다.

ssh host '
  zmodload zsh/zselect
  cmd 3> >(zselect -r 0 -r 1; kill -s HUP 0)'

zselectcmd 종료를 감지하기 위해 stdin(cmd의 fd 3에 대한 파이프)에 EOF가 표시되거나 stdout에서 손상된 파이프가 감지되면 반환됩니다. 그런 다음 cmd서브쉘과 쉘을 대체하는 아직 실행 중인 프로세스를 포함하여 전체 프로세스 그룹(0)을 종료합니다 .

답변3

tail작성하지 않더라도 운영 체제는 표준 출력이 파이프가 끊어졌는지 알 수 없을 수도 있습니다. 바라보다이 답변도착하다tail -f … | grep -q …일치하는 항목을 찾은 후에도 종료되지 않는 이유는 무엇입니까?

최신 버전은 tailGNU Coreutils에서 볼 수 있습니다.

만약에당신은 tail정말 똑똑해요만약에stdout이 파이프인지 확인한 다음 tail -f /dev/null스크립트에서 실행합니다. 파이프가 끊어지면 즉시 명령이 종료됩니다.

개념 증명( tail예: GNU Coreutils의 "smart" 필요):

sh -c 'tail -f /dev/null; echo >&2 "Pipe broken!"' | sleep 5
#      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^    this is our script
#                                                  ^ this pipe will break after 5 seconds

노트:

  • tail -f /dev/null아무것도 인쇄되지 않습니다.
  • stdout이 일반 파일이라면 tail -f /dev/null,아니요항상 스스로 종료하십시오.
  • 저는 tailGNU Coreutils 8.32를 사용하여 Kubuntu 22.10에서 테스트했습니다.
  • 비교해 보세요. busybox tail -f /dev/null"스마트"하지 않고 파이프가 파손된 후에도 그대로 유지됩니다.

답변4

내 생각에는 그 튜브가 부러질 것 같지 않다. Bash에서 OR 연산자(||)를 사용하면 일반적으로 조건문(if 문)에 사용되기 때문에 거의 항상 무시됩니다.

이 프로그램이 단지 다른 프로그램의 테스트일 뿐이라면 루프를 사용하는 것이 좋습니다 for.

char='1 2 3 4 5' # Change this to whatever you want
for i in $char; do
  printf "Something"
done

범위를 만들 수도 있습니다.

for i in {1..[your number]}; do
  printf "Something"
done

도움이 되길 바랍니다. 행운을 빌어요!

관련 정보