当来自终端的 运行 和来自 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
内置函数,如下所示(这将有利于取代 fold
和 head
):
#!/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)。
我有一个简短的 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
内置函数,如下所示(这将有利于取代 fold
和 head
):
#!/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)。