8

I'm trying to call a shell script in python, but it keeps reporting broken pipe error (the result is OK, but i don't want to see the error message in STDERR). I have pinpointed the cause, and it can be reproduced as the following snippet:

subprocess.call('cat /dev/zero | head -c 10 | base64', shell=True)

AAAAAAAAAAAAAA==

cat: write error: Broken pipe

/dev/zero is an infinite stream, but the head -c 10 only reads 10 bytes from it and exits, then cat will get SIGPIPE because of the peer has closed the pipe. There's no broken pipe error message when i run the command in shell, but why python shows it?

5
  • 4
    The error goes away when you skip the uuoc: subprocess.call('head -c 10 < /dev/zero | base64', shell=True) Commented May 7, 2012 at 10:05
  • 2
    @larsmans: you could put that as an answer Commented May 7, 2012 at 10:30
  • @ChrisMorgan: actually, I strongly prefer your answer. Commented May 7, 2012 at 10:46
  • @larsmans: it depends on what he's doing. If it's not just simple things like head and base64, it may be too difficult to manage in pure Python. Yours solves the question as asked, which is of value. Commented May 7, 2012 at 10:47
  • @ChrisMorgan: true. Still, I would only post an answer if I understood what was happening, and I don't. I've never seen cat complain about a broken pipe before, and have always relied on it exiting silently when the pipe is closed. Commented May 7, 2012 at 10:51

2 Answers 2

9

The default action to SIGPIPE signal is to terminate the program. Python interpreter changes it to SIG_IGN to be able to report broken pipe errors to a program in the form of exceptions.

When you execute cat ... |head ... in shell, cat has default SIGPIPE handler, and OS kernel just terminates it on SIGPIPE.

When you execute cat using subprocess it derives SIGPIPE handler from its parent (python interpreter), SIGPIPE is just ignored and cat handles the error itself by checking errno variable and printing error message.

To avoid error messages from cat you can use preexec_fn argument to subprocess.call:

from signal import signal, SIGPIPE, SIG_DFL
subprocess.call(
    'cat /dev/zero | head -c 10 | base64',
    shell = True,
    preexec_fn = lambda: signal(SIGPIPE, SIG_DFL)
)
Sign up to request clarification or add additional context in comments.

2 Comments

Excellent, thanks very much for that, just solved my problem with a shell script using cat (and I'm fairly sure it's not a uuoc, maybe that's another question!).
Excellent! Fixed a tr: broken pipe error caused by calling a bash script with subprocess.call()
2

In this trivial case at least you're not gaining anything by using shell commands—and you're losing portability and speed.

Python 2 code:

>>> import base64
>>> base64.b64encode(open('/dev/zero', 'rb').read(10))
'AAAAAAAAAAAAAA=='
>>> base64.b64encode('\0' * 10)
'AAAAAAAAAAAAAA=='

In Python 3 (code will also run in 2.6+, though it will return str rather than bytes instances):

>>> import base64
>>> base64.b64encode(open('/dev/zero', 'rb').read(10))
b'AAAAAAAAAAAAAA=='
>>> base64.b64encode(b'\0' * 10)
b'AAAAAAAAAAAAAA=='

In each case, the first example retains the usage of /dev/zero (in itself non-portable, but never mind), the second produces the effect, though I imagine it's not what you want specifically?

2 Comments

+1. '\x00' can also be written '\0' (or b'\0' in Python 3.x).
I think I prefer subdir's answer, which addresses the question directly, and explains the problem.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.