Answering this old question for the sake of completeness, as none of the answers suggest splitting the commands nor give an answer that works with shell=False
Practical solution: two calls to subprocess.run
or subprocess.Popen
+ Popen.communicate
Note this ignores stderr
import subprocess, shlex
proc1 = subprocess.Popen(shlex.split("echo a"), stdout=subprocess.PIPE)
(proc1_stdout, _) = process.communicate()
print(proc1_stdout, end="")
proc2 = subprocess.Popen(shlex.split("echo b"), stdout=subprocess.PIPE)
(proc2_stdout, _) = process.communicate()
print(proc2_stdout, end="")
This handles executing chained commands through a single subprocess.run
call decently well without relying on shell=True
.
However, this is needlessly complicated
import shlex
import subprocess
import sys
import warnings
def subprocess_cmd(command, text=True, posix=False):
tokens: shlex.shlex = shlex.shlex(command, posix=posix, punctuation_chars=True)
commands: list[list[str]] = [[]]
separators: list[str] = [";"]
for token in tokens:
if token in ("(", ")"):
warnings.warn("Parentheses are not implemented and will be ignored", RuntimeWarning)
if token in ("|", ";", "||", "&&"):
separators.append(token)
commands.append([])
else:
commands[-1].append(token)
out: str|bytes = "" if text else b""
err: str|bytes = "" if text else b""
ret: int = 0
for (command, separator) in zip(commands, separators):
if (separator in (";")) or (separator in ("|", "||") and not ret == 0) or (separator in ("&&") and ret == 0):
if separator != "|": print(out, end="")
print(err, end="", file=sys.stderr)
if separator == "|":
result = subprocess.run(command, input=out, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=text)
else:
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=text)
out = result.stdout
err = result.stderr
ret = result.returncode
print(out, end="")
print(err, end="", file=sys.stderr)
return ret