The shortest answer to your problem/question as stated: Replace subprocess.Popen
with subprocess.call
or one of its (for instance checking) variants. Or add process.communicate()
.
What happened and why did it look like it "would not work". Popen
opened pipes for communication and forked a process as desired. However, the pipe does not have anything reading from it on the parent process side (the one you called it from) which can actually cause the child process (write to stdout/stderr) very quickly land in a blocking I/O. Meanwhile your parent continues to run as there is nothing telling it to wait for its child and eventually terminates at which point the child process receives SIGPIPE
(to which default action is to terminate).
Let's have a test.sh
:
#!/bin/bash
handle_sigpipe() {
echo "GOT SIGPIPE" >> OUT
exit 0
}
trap handle_sigpipe SIGPIPE
echo line1 > OUT
echo line2
echo line3 >> OUT
and a small python script calling it similar to what is in your question:
import time
import subprocess
try:
time.sleep(20)
except KeyboardInterrupt:
cmd = "./test.sh"
process = subprocess.Popen(cmd, shell=True,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
Now we call and interrupt it:
$ python3 test.py
^C
$ cat OUT
line1
GOT SIGPIPE
We've wrote line1
, but when script tries to write line2
, it just ends up waiting for someone to read from the receiving and of the pipe (at least when called through shell, these stdout writes are line buffered). In the meanwhile, parent finishes up and closes its end of the pipe. test.sh
receives SIGPIPE
, handler writes it to the file and the shell script terminates.
If you actually actually generally want to perform clean-up/save your work on script exit (also when interrupted). atexit
is the common way to do that. If you wanted to generally handle a specific signal (like SIGINT
), you could also have a look at signal.signal
.