Script works differently when running from a terminal and running from Python

I have a short bash script 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 works fine, ending when it is done

 $ ./foo.sh m1un $ 

but when i run it from python

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

it prints a string, but then just hangs forever. What causes this mismatch?

+8
python bash subprocess pipeline
source share
2 answers

Adding the trap -p command to the bash script, stopping the hung python process, and starting ps shows what happens:

 $ 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 in the mask. When head executes its task and exits, other processes do not receive a broken channel signal and do not interrupt.

With an explanation of the problem at hand, it was easy to find the bug in python bugtracker, which turned out to be issue # 1652 .

+8
source share

The problem with Python 2 processing SIGPIPE non-standard way (i.e., is ignored) is already written out in Leon's answer, and the correction is given in the link: set SIGPIPE by default ( SIG_DFL ), for example,

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

You can try to disable SIGPIPE from your script with, for example,

 #!/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 according to the Bash reference guide

Signals ignored when entering the shell cannot be captured or reset.


Last comment: you have useless use of cat here; better to write a script like:

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

However, since you are using Bash, you can use the built-in read construct as follows (this will advantageously replace fold and head ):

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

It turns out that with this version you will have a clear idea of ​​what is happening (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, this works well without errors with Python3). And telling Python to use the default signal for SIGPIPE also works well:

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

(and also works with Python3).

+1
source share

All Articles