当来自终端的 运行 和来自 Python 的 运行 时,脚本的工作方式不同

Script works differently when ran from the terminal and ran from Python

我有一个简短的 bash 脚本 foo.sh

#!/bin/bash

cat /dev/urandom | tr -dc 'a-z1-9' | fold -w 4 | head -n 1

当我直接从 shell 运行 时,它 运行 没问题,完成后退出

$ ./foo.sh 
m1un
$

但是当我 运行 它来自 Python

$ python -c "import subprocess; subprocess.call(['./foo.sh'])"
ygs9

它输出该行,但随后永远挂起。是什么导致了这种差异?

trap -p 命令添加到 bash 脚本,停止挂起的 python 进程和 运行 ps 显示发生了什么:

$ cat foo.sh
#!/bin/bash

trap -p
cat /dev/urandom | tr -dc 'a-z1-9' | fold -w 4 | head -n 1

$ python -c "import subprocess; subprocess.call(['./foo.sh'])"
trap -- '' SIGPIPE
trap -- '' SIGXFSZ
ko5o

^Z
[1]+  Stopped     python -c "import subprocess; subprocess.call(['./foo.sh'])"
$ ps -H -o comm
COMMAND
bash
  python
    foo.sh
      cat
      tr
      fold
  ps

因此,subprocess.call() 执行屏蔽了 SIGPIPE 信号的命令。当 head 完成其工作并退出时,其余进程不会收到 broken pipe 信号并且不会终止。

有了手头问题的解释,很容易在 python 错误追踪器中找到错误,结果是 issue#1652

Leon 的回答中已经提出了 Python 2 以非标准方式处理 SIGPIPE 的问题(即被忽略),修复方法在 link:将 SIGPIPE 设置为默认值 (SIG_DFL),例如

import signal
signal.signal(signal.SIGPIPE,signal.SIG_DFL)

您可以尝试从您的脚本中取消设置 SIGPIPE,例如,

#!/bin/bash

trap SIGPIPE # reset SIGPIPE

cat /dev/urandom | tr -dc 'a-z1-9' | fold -w 4 | head -n 1

但是,不幸的是,根据 Bash reference manual

,它不起作用

Signals ignored upon entry to the shell cannot be trapped or reset.


最后的评论:你在这里使用 cat 没有用;最好将脚本编写为:

#!/bin/bash

tr -dc 'a-z1-9' < /dev/urandom | fold -w 4 | head -n 1

然而,由于您使用的是 Bash,您不妨使用 read 内置函数,如下所示(这将有利于取代 foldhead):

#!/bin/bash

read -n4 a < <(tr -dc 'a-z1-9' < /dev/urandom)
printf '%s\n' "$a"

事实证明,使用此版本,您将清楚地知道发生了什么(并且脚本不会挂起):

$ python -c "import subprocess; subprocess.call(['./foo'])"
hcwh
tr: write error: Broken pipe
tr: write error
$
$ # script didn't hang

(当然,用Python3 也能正常工作,没有错误)。并告诉 Python 使用 SIGPIPE 的默认信号也很有效:

$ python -c "import signal; import subprocess; signal.signal(signal.SIGPIPE,signal.SIG_DFL); subprocess.call(['./foo'])"
jc1p
$

(也适用于 Python3)。