Tadeck's answer can get you most of the way there; but one big remaining problem is the broken-pipe error killing off py_prog.py
.
It turns out that catching broken pipes for sys.stdout
is tricky, because sometimes they occur during shutdown when it is too late.
If my_prog.py
is relatively clean you can wrap it with some special boilerplate. Let's say, for illustration purposes, that it looks something like this trivial program:
$ cat badpipe.py
import sys
def main():
for i in range(1000):
print 'line', i
return 0
if __name__ == '__main__':
try:
sys.exit(main())
except KeyboardInterrupt:
sys.exit('\nInterrupted')
The code under the __name__ == '__main__'
test at the end is—or has been, anyway—my usual boilerplate for my standalone python programs. It turns out I may need to change it, based on this answer.
Anyway, if I try running this with two "bad" cases, one that exits immediately and one that reads a bit and then exits, it behaves two different ways. First, pipe into "exit immediately":
$ python badpipe.py | (exit 0)
Traceback (most recent call last):
File "badpipe.py", line 10, in <module>
sys.exit(main())
File "badpipe.py", line 5, in main
print 'line', i
IOError: [Errno 32] Broken pipe
That's about what I expected. But:
$ python badpipe.py | head -1
line 0
close failed in file object destructor:
sys.excepthook is missing
lost sys.stderr
Whoa! Freaky! :-)
It turns out I can get the freaky behavior (lost sys.stderr
) with the head -1
to go away by tuning up my wrapper a bit. Instead of just sys.exit(main())
, I need to invoke sys.stdout.flush()
(ideally, maybe also sys.stderr.flush()
too, but so far I have only tested this much) before calling sys.exit
:
if __name__ == '__main__':
try:
ret = main()
except KeyboardInterrupt:
ret = '\nInterrupted'
try:
sys.stdout.flush()
finally:
sys.exit(ret)
With this in place, I can now reliably catch the outermost IOError
and check for a broken-pipe case. Here's the final (more or less) version, including main
again:
import errno, sys
def main():
for i in range(1000):
print 'line', i
return 0
if __name__ == '__main__':
ret = 0
try:
try:
ret = main()
except KeyboardInterrupt:
ret = '\nInterrupted'
finally:
sys.stdout.flush()
except IOError as err:
if err.errno == errno.EPIPE:
sys.stderr.write('caught pipe-based IO-error\n')
ret = 123 # or whatever
else:
raise # some other I/O error; attempt to get a traceback
finally:
sys.exit(ret)
The sys.stderr.write
after catching EPIPE
, and altered ret
value, are mainly for illustration—there's nothing special about 123. Also I don't know if the final raise
works right, as I have not tested it.
Running this gives:
$ python badpipe.py | (exit 0)
caught pipe-based IO-error
$ python badpipe.py | (head -1)
line 0
caught pipe-based IO-error
$
(Note: this is all in python 2.7 but 3.2 behaves similarly.)
This is all well and good if you can modify py_prog.py
, but what if you can't?
In that case, I'd suggest writing a wrapper script (in whatever language, Python will work fine). Have your wrapper script read all its stdin and copy (i.e., write) it all to stdout but check for (catch) the broken-pipe error. If and when that occurs, change strategy: read the rest of stdin and just throw it away, so that py_prog.py
happily believes it managed to send everything to stdout and complete. You can even make it write it to a subprocess.Popen
that runs your java scalaProg.pkg
command and does all the desired special logging case for you.
You might want to write this wrapper even if you can modify py_prog.py
, depending on exactly what you want to have happen.
python py_prog.py | python wrap_java_thing.py
(I'm not going to write the wrapper for you though :-) )
Incidentally, the lost sys.stderr
thing is a Python bug, issue 11380. An easy way to provoke it: python -c 'print "foo\n"*10000' | head -1