I'm using this code, which is supposed to be transitional for python2 to python3 porting phase (I know there are third-party libs for run
, I want to implement my own to collect experience)
def run(args,
stdin=None, input=None,
stdout=None, stderr=None, capture_output=False,
timeout=None,
encoding=None,
**popen_kwargs):
# create stderr and stdout pipes, if capture_output is true
if capture_output:
_stderr = subprocess.PIPE
_stdout = subprocess.PIPE
else:
_stdout, _stderr = stdout, stderr
# if input is given, create stdin as pipe, where input will be passed into
if input is not None:
_stdin = subprocess.PIPE
else:
_stdin = stdin
# this starts the process. python2 did not have 'encoding'
if sys.version_info.major >= 3:
proc = subprocess.Popen(args, stdin=_stdin, stdout=_stdout, stderr=_stderr, encoding=encoding,
**popen_kwargs)
else:
proc = subprocess.Popen(
args, stdin=_stdin, stdout=_stdout, stderr=_stderr, **popen_kwargs)
# run a background timer to interrupt 'communicate', if necessary
if timeout is not None:
def cancel():
try:
proc.terminate()
except OSError:
# an exception here means that the process is gone already
pass
cancel_timer = Timer(timeout, cancel)
cancel_timer.start()
# special case for python2 for which we allow passing 'unicode' if an encoding is used
if input is not None and sys.version_info.major < 3:
if type(input) == unicode:
import codecs
input = codecs.encode(input, encoding)
(stdoutoutput, stderroutput) = proc.communicate(input)
# check timeout scenario
if timeout is not None:
if not cancel_timer.is_alive():
raise TimeoutExpired(args, timeout, stdoutoutput,
stdoutoutput, stderroutput)
else:
cancel_timer.cancel()
cancel_timer.join()
# on python2, outputs will always be 'str', which is fine with us, as it's the union of
# str and bytes
return CompletedProcess(args, proc.poll(), stdoutoutput, stderroutput)
However, the code blocks indefinitely within communicate
whenever I try to execute anything with the shell builtin time
prefixed and capture_output
on both python2 and python3. It must be something really stupid.
>>> run("time sleep 5m", shell=True, capture_output=True, timeout=1)
... (^ C to stop it)
>>> run("sleep 5m", shell=True, capture_output=True, timeout=1)
zsubprocess.TimeoutExpired: sleep 5m: Timeout after 1 seconds
I do not understand why that is the case.