简体   繁体   English

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

[英]Script works differently when ran from the terminal and ran from Python

I have a short bash script foo.sh 我有一个简短的bash脚本foo.sh

#!/bin/bash

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

When I run it directly from the shell, it runs fine, exiting when it is done 当我直接从shell运行它时,它运行正常,完成时退出

$ ./foo.sh 
m1un
$

but when I run it from Python 但是当我从Python运行它时

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

it outputs the line but then just hangs forever. 它输出线但然后永远挂起。 What is causing this discrepancy? 造成这种差异的原因是什么?

Adding the trap -p command to the bash script, stopping the hung python process and running ps shows what's going on: 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

Thus, subprocess.call() executes the command with the SIGPIPE signal masked. 因此, subprocess.call()执行命令,并屏蔽SIGPIPE信号。 When head does its job and exits, the remaining processes do not receive the broken pipe signal and do not terminate. head完成其工作并退出时,其余进程不会收到损坏的管道信号并且不会终止。

Having the explanation of the problem at hand, it was easy to find the bug in the python bugtracker, which turned out to be issue#1652 . 解释了手头的问题,很容易在python bugtracker中找到bug,结果证明是问题#1652

The problem with Python 2 handling SIGPIPE in a non-standard way (ie, being ignored) is already coined in Leon's answer, and the fix is given in the link: set SIGPIPE to default ( SIG_DFL ) with, eg, Python 2以非标准的方式处理SIGPIPE的问题(即被忽略)已经在Leon的答案中被创造出来了,修复在链接中给出:将SIGPIPE设置为默认值( SIG_DFL ),例如,

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

You can try to unset SIGPIPE from within your script with, eg, 您可以尝试从脚本中取消设置SIGPIPE ,例如,

#!/bin/bash

trap SIGPIPE # reset SIGPIPE

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

but, unfortunately, it doesn't work, as per the Bash reference manual 但是,不幸的是,根据Bash参考手册 ,它不起作用

Signals ignored upon entry to the shell cannot be trapped or reset. 进入shell时忽略的信号不能被捕获或重置。


A final comment: you have a useless use of cat here; 最后的评论:你在这里使用了无用的cat ; it's better to write your script as: 最好将脚本编写为:

#!/bin/bash

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

Yet, since you're using Bash, you might as well use the read builtin as follows (this will advantageously replace fold and head ): 然而,既然你正在使用Bash,你也可以使用如下内置的read (这将有利地取代foldhead ):

#!/bin/bash

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

It turns out that with this version, you'll have a clear idea of what's going on (and the script will not hang): 事实证明,使用此版本,您将清楚地知道发生了什么(并且脚本不会挂起):

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

(Of course, it works well with no errors with Python3). (当然,它适用于Python3没有错误)。 And telling Python to use the default signal for SIGPIPE works well too: 告诉Python使用SIGPIPE的默认信号也很有效:

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

(and also works with Python3). (也适用于Python3)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM