linux Bash和Python的信号行为与管道不一致

ctrmrzij  于 2023-11-17  发布在  Linux
关注(0)|答案(1)|浏览(101)

我试着从管道的另一边接收信号,但我似乎接收不到任何信号
这个程序是用来捕捉

#! /usr/bin/python
import signal
import sys

from time import sleep

def signal_handler(sig, frame):
    print('You pressed Ctrl+C!')
    sys.exit(0)

for i in [x for x in dir(signal) if x.startswith("SIG")]:
  try:
    signum = getattr(signal,i)
    signal.signal(signum,signal_handler)
  except (OSError, RuntimeError) as m: #OSError for Python3, RuntimeError for 2
    print ("Skipping {}".format(i))
  except ValueError:
      print ("Value skip {}".format(i))

signal.signal(signal.SIGINT, signal_handler)

for a in range(0, 20):
    print a
    sleep(1)

字符串
我得到了不同运行方法的不一致行为,我不确定该如何解释它。当我将脚本的输出通过管道传输到tee时,我无法捕捉到可以干净地结束程序的信号
运行方式:运行脚本,手动Ctrl+C

/sub.py


输出量:

Skipping SIGKILL
Skipping SIGSTOP
Value skip SIG_DFL
0
1
2
3
4
^CYou pressed Ctrl+C!


运行方法(运行脚本并使用kill -13(BROKEN_PIPE)杀死):
输出量:

Skipping SIGKILL
Skipping SIGSTOP
Value skip SIG_DFL
0
1
2
3
4
You pressed Ctrl+C!


运行方法(使用管道连接到T形三通和Ctrl+C运行脚本):

/sub.py | tee


输出量:

Skipping SIGKILL
Skipping SIGSTOP
Value skip SIG_DFL
0
^CTraceback (most recent call last):
  File "/sub.py", line 24, in <module>
    sleep(1)


Skipping SIGKILL
Skipping SIGSTOP
Value skip SIG_DFL
0
1
2
3
4
^C


注意,在这两种情况下都没有调用signal_handler方法。
为什么没有调用信号处理程序?是否有一个信号我可以捕捉到优雅退出?

python --version
Python 2.7.17
bash --version
GNU bash, version 4.4.20(1)-release (x86_64-pc-linux-gnu)
xam8gpfp

xam8gpfp1#

这个答案主要针对python 3.7+(在这里讨论),但我想python 2.7也是如此。
在信号处理程序中调用print或在stdout上使用未刷新的数据调用sys.exit()都不起作用,因为到那时,tee已经停止,stdout管道已经损坏(当您Ctrl-C管道时会发生什么,请参见here)。
我认为最简单的解决方法是避免使用你打算中断的管道。你可以通过使用tail而不是tee来实现这一点:

tail -F tmp.txt & python writer.py > tmp.txt && sleep 1; kill $!

字符串
如果你确实想让shell管道工作,你可以尝试重写你的信号处理程序,把你的消息和未刷新的stdout数据写到一个像这样的文件中:

def signal_handler(sig, frame):
    f = os.open('buffer.txt', os.O_WRONLY | os.O_CREAT)
    os.dup2(f, sys.stdout.fileno())
    sys.stdout.flush()
    os.write(f, "You pressed Ctrl-C!".encode())
    os.close(f)
    sys.exit(1)


这里需要注意的是,这个解决方案可能有一个小的竞争条件。如果stdout管道首先被破坏,然后你的程序在收到SIGTERM信号之前尝试刷新到stdout,你可能会得到一个BrokenPipeError,然后是一个SIGTERM信号,这将是一个混乱的尝试处理。

相关问题