从终端运行并从Python运行时,脚本的工作方式不同

时间:2016-09-08 17:32:51

标签: python bash subprocess pipeline

我有一个简短的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

它输出线但后来永远挂起。造成这种差异的原因是什么?

2 个答案:

答案 0 :(得分:8)

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完成其工作并退出时,其余进程不会收到损坏的管道信号,也不会终止。

对手头的问题进行了解释,很容易在python bugtracker中找到bug,结果证明是issue#1652

答案 1 :(得分:1)

Python 2以非标准方式处理SIGPIPE的问题(即被忽略)已经在Leon的答案中被创造出来了,修复在链接中给出:将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

,它不起作用
  

进入shell时忽略的信号无法被捕获或重置。

最后的评论:你在这里无用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)。