Bash 스크립트에서 깨진 파이프의 프로세스 종료

Bash 스크립트에서 깨진 파이프의 프로세스 종료

코드를 고려해보세요:

printf '%s\n' 1 2 3 4 5 | head -n 2

출력은 다음과 같습니다.

1
2

내가 이해한 바로는 head처음 두 줄을 읽은 후 프로세스가 파이프를 손상시키면 printf프로세스가 손상된 파이프를 포착하고 정상적으로 종료된다는 것입니다.

내 스크립트 중 하나에서는 손상된 파이프에서 정상적으로 종료되지 않고 대신 설정된 작업이 완료되거나 다른 이유로 종료될 때까지 계속 실행되는 Python 애플리케이션을 사용합니다. 표준 출력에 쓰려고 시도하고 실패할 때마다 불평합니다 broken pipe.

이것기사파이썬에서 깨진 파이프를 처리하는 방법을 설명하세요.

응용 프로그램 개발자에게 깨진 파이프 처리를 구현하도록 권장할 수도 있습니다. 어떤 이유로 나는 그들이 그럴 것이라고 의심합니다. 아마도 응용 프로그램을 포크할 수도 있지만 꽤 복잡해서 원하지 않을 것입니다.

내가 남긴 유일한 옵션은 쉘(Bash)이 파이프에 쓰려고 할 때 프로세스를 종료하도록 하는 방법을 찾는 것입니다. 가능합니까? 그렇다면 어떻게 해야 합니까?

답변1

write()예, python3에는 SIGPIPE를 무시하고 EPIPE가 실패할 때 예외를 발생시키는 성가신 동작이 있습니다 . 깨진 파이프가 수명과 정상 작동의 일부임에도 불구하고 말입니다.

출력을 전달하고 SIGTERM을 사용하여 python3을 종료하는 래퍼를 사용하면 실제로 이 문제를 해결할 수 있습니다(예를 들어 출력이 파이프가 끊어진 경우).

bash하지만 이것은 제가 이런 종류의 작업에 사용하는 쉘이 아닙니다.

당신이 사용할 수있는 perl:

perl -e '
  $pid = open CMD, "-|", @ARGV;
  $SIG{PIPE} = "IGNORE";
  while (sysread CMD, $buf, 8192) {
    if (!syswrite STDOUT, $buf) {
      kill "TERM", $pid;
      last;
    }
  }
  close CMD;
  exit($? & 127 ? ($? & 127) | 128 : $? >> 8)' -- your-python-program

쉘이 필수라면 zsh가 더 나은 선택이 될 것입니다.

zsh -c '
  zmodload zsh/system
  coproc {"$0" "$@" <&3 3<&-} 3<&0
  trap "" PIPE
  while sysread -s 8192 buf <&p; do
    syswrite -- $buf || {
      kill $! 2> /dev/null
      break
    }
  done
  wait $!' your-python-program

예:

$ python3 -uc 'import time; print("foo"); time.sleep(1); print("bar")' | head -n1
foo
Traceback (most recent call last):
  File "<string>", line 1, in <module>
BrokenPipeError: [Errno 32] Broken pipe
$ zsh -c that-code python3 -uc 'import time; print("foo"); time.sleep(1); print("bar")' | head -n1
foo
$ echo $pipestatus
143 0
$ kill -l 143
TERM

이제 python3의 성가심을 해결하기 위해 프로세스가 추가 파이프를 통해 출력을 푸시하는 데 시간을 소비하는 것은 과도한 것처럼 보입니다.

python3또 다른 접근 방식 은 처음부터 이러한 신호가 무시되는 것을 방지하는 것입니다 .

$ strace -qqqZ -e signal=none -e rt_sigaction -e inject=rt_sigaction:retval=0 python3 -uc 'import time; print("foo"); time.sleep(1); print("bar")' | head -n1
foo

모든 Python3 시스템 호출 호출을 효과적으로 strace단락시켜 rt_sigaction()설치를 방지합니다.어느신호 처리기 또는 변경 신호 처리(모든 신호).

따라서 python3 스크립트를 중지할 때 표시되는 성가신 메시지도 제거되지만 ^C, Python 스크립트가 종료되었을 때 정리하기 위해 일부 신호 처리기를 설치하는 경우 위험할 수 있습니다.

SIGPIPE 신호에 대한 호출에 대해서만 이 작업을 수행하는 것이 더 좋지만, 내가 아는 한 strace는 이를 수행할 수 없습니다. 그러나 다음과 $LD_PRELOAD같은 몇 가지 트릭을 사용하여 이를 달성 할 수 있습니다 .

$ cat leave-sigpipe-alone.c
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>

int sigaction(int signum, const struct sigaction * restrict act, struct sigaction * restrict oldact)
{
  static int (*orig_sigaction)(int, const struct sigaction * restrict, struct sigaction * restrict) = 0;

  if (!orig_sigaction)
    orig_sigaction = (int (*)(int, const struct sigaction * restrict, struct sigaction * restrict)) dlsym (RTLD_NEXT, "sigaction");

  if (signum == SIGPIPE) return 0;
  return orig_sigaction(signum, act, oldact);
}
$ gcc -fPIC -shared -o leave-sigpipe-alone.so leave-sigpipe-alone.c -ldl
$ LD_PRELOAD=$PWD/leave-sigpipe-alone.so python3 -uc 'import time; print("foo"); time.sleep(1); print("bar")' | head -n1
foo
$ echo $pipestatus
141 0
$ kill -l 141
PIPE

python3정상적으로 작동하는 실행 파일과 마찬가지로 SIGPIPE에 의해 자동으로 종료됩니다.

strace-f옵션이 있든 없든 sigaction()호출은 상위 프로세스(실행 프로세스)에 의해서만 차단됩니다 python3. 개별 명령을 실행하더라도 계속해서 이를 가로채지만 하위 명령 내에서는 그렇게 하지 않습니다(해당 -f옵션을 전달하지 않는 한). 이 LD_PRELOAD트릭은 개별 명령을 실행한 후에도(해당 명령이 동적으로 연결된 경우) 모든 하위 프로세스에 영향을 미칩니다.

python3 프로그래밍 언어(대부분의 사람들이 사용하는 언어)용 cpython 인터프리터와 함께 사용하기 위한 보다 깔끔한 접근 방식은 사용자 정의에서 SIGPIPE의 기본 신호 구성을 복원하는 것입니다 sitecustomize.py.

$ cat ~/lib/python3-leave-sigpipe-alone/sitecustomize.py
from signal import signal, SIGPIPE, SIG_DFL
signal(SIGPIPE,SIG_DFL)
$ PYTHONPATH=~/lib/python3-leave-sigpipe-alone python3 -uc 'import time; print("foo"); time.sleep(0.2); print("bar")' | head -n1
foo
$ echo $pipestatus
141 0

여기서는 시스템 디렉터리를 변경하는 대신 sitecusomize.py전용 디렉터리의 디렉터리를 변경하므로 PYTHONPATH손상된 파이프에 쓰려고 할 때 SIGPIPE에 의해 종료되기를 원하는 python3 스크립트에 대해서만 변수를 해당 디렉터리로 설정할 수 있습니다.

그것은 적용되지 않는 것 같습니다 pypy3.

관련 정보