Script works differently when ran from the terminal and ran from Python
<p>I have a short bash script <code>foo.sh</code></p>

<pre><code>#!/bin/bash

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

<p>When I run it directly from the shell, it runs fine, exiting when it is done</p>

<pre><code>$ ./foo.sh 
m1un
$
</code></pre>

<p>but when I run it from Python</p>

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

<p>it outputs the line but then just hangs forever. What is causing this discrepancy? </p>

<p>The problem with Python 2 handling <code>SIGPIPE</code> in a non-standard way (i.e., being ignored) is already coined in Leon's answer, and the fix is given in the link: set <code>SIGPIPE</code> to default (<code>SIG_DFL</code>) with, e.g.,</p>

<pre><code>import signal
signal.signal(signal.SIGPIPE,signal.SIG_DFL)
</code></pre>

<hr>

<p>You can try to unset <code>SIGPIPE</code> from within your script with, e.g.,</p>

<pre><code>#!/bin/bash

trap SIGPIPE # reset SIGPIPE

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

<p>but, unfortunately, it doesn't work, as per the <a href="https://www.gnu.org/software/bash/manual/bashref.html#index-trap" rel="nofollow">Bash reference manual</a></p>

<blockquote>
  <p>Signals ignored upon entry to the shell cannot be trapped or reset.</p>
</blockquote>

<hr>

<p>A final comment: you have a useless use of <code>cat</code> here; it's better to write your script as:</p>

<pre><code>#!/bin/bash

tr -dc 'a-z1-9' &lt; /dev/urandom | fold -w 4 | head -n 1
</code></pre>

<p>Yet, since you're using Bash, you might as well use the <code>read</code> builtin as follows (this will advantageously replace <code>fold</code> and <code>head</code>):</p>

<pre><code>#!/bin/bash

read -n4 a &lt; &lt;(tr -dc 'a-z1-9' &lt; /dev/urandom)
printf '%s\n' "$a"
</code></pre>

<p>It turns out that with this version, you'll have a clear idea of what's going on (and the script will not hang):</p>

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

<p>(Of course, it works well with no errors with Python3). And telling Python to use the default signal for <code>SIGPIPE</code> works well too:</p>

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

<p>(and also works with Python3).</p>

python bash subprocess pipeline