running multiple bash commands with subprocess
Asked Answered
J

9

93

If I run echo a; echo b in bash the result will be that both commands are run. However if I use subprocess then the first command is run, printing out the whole of the rest of the line. The code below echos a; echo b instead of a b, how do I get it to run both commands?

import subprocess, shlex
def subprocess_cmd(command):
    process = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE)
    proc_stdout = process.communicate()[0].strip() 
    print proc_stdout

subprocess_cmd("echo a; echo b")
Jello answered 19/7, 2013 at 9:36 Comment(1)
A
129

You have to use shell=True in subprocess and no shlex.split:

import subprocess

command = "echo a; echo b"

ret = subprocess.run(command, capture_output=True, shell=True)

# before Python 3.7:
# ret = subprocess.run(command, stdout=subprocess.PIPE, shell=True)

print(ret.stdout.decode())

returns:

a
b
Alethaalethea answered 19/7, 2013 at 10:11 Comment(7)
Ah I see, I had shell=True tested but command.split() was breaking it (list). Also to note to other people reading, using shell=True is a security hazard, make sure you trust the input.Jello
You cannot use command.split() with shell=True. Actually the argument of subprocess.Popen with shell=True must be a string and not a list.Alethaalethea
I found this to be a bit simpler and didn't have the garbling of the output: def do_shell(self, command): self.proc=subprocess.Popen(command,shell=True) self.proc.wait()Grearson
@Ravichandra: notice: bash in the title. On Windows, you probably want echo a & echo b command instead.Radmen
Your result runs /bin/sh, not /bin/bash. Add executable='/bin/bash' to the Popen() args to get a bash shell.Curule
On windows this does not work, it returns "a; echo b"Kantor
@Milan, on windows, the default shell is batch, which does not honor ';' as a separator.Billiton
E
33

I just stumbled on a situation where I needed to run a bunch of lines of bash code (not separated with semicolons) from within python. In this scenario the proposed solutions do not help. One approach would be to save a file and then run it with Popen, but it wasn't possible in my situation.

What I ended up doing is something like:

commands = '''
echo "a"
echo "b"
echo "c"
echo "d"
'''

process = subprocess.Popen('/bin/bash', stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True)
out, err = process.communicate(commands)
print(out)

So I first create the child bash process and after I tell it what to execute. This approach removes the limitations of passing the command directly to the Popen constructor.

The text=True addition is required for Python 3.

Extempore answered 26/3, 2015 at 1:1 Comment(4)
subprocess.check_output(commands, shell=True) works just fine. If there are bash-isms in commands then pass executable='/bin/bash'.Radmen
For Python 3 you might want to use: out, err = process.communicate(commands.encode('utf-8')) and print(out.decode('utf-8'))Tyree
If the commands are ; separated you might also want to add shell=True to the subprocess.Popen() line.Thievery
Modern Python should use subprocess.run(commands, shell=True). Like the Popen documentation already tells you, avoid it when you can, such as when all you want to do is run a subprocess and wait for it to finish.Zina
P
21

Join commands with "&&".

os.system('echo a > outputa.txt && echo b > outputb.txt')
Pinprick answered 29/8, 2016 at 21:25 Comment(5)
The second command would not run if the first command has an error. I would say use an ; instead.Theseus
The important part here is to use os.system() instead of subprocess.Seigneury
os.system is not a substitute for subprocess. os.system cannot do all the things that subprocess does.Balalaika
Maybe not, but it can echo just fine.Deemphasize
subprocess is preferred to os.system, but no one has given an answer that works with subprocess and shell=False (or analogous).Exaggerative
V
4

If you're only running the commands in one shot then you can just use subprocess.check_output convenience function:

def subprocess_cmd(command):
    output = subprocess.check_output(command, shell=True)
    print output
Vertical answered 21/2, 2017 at 19:16 Comment(0)
T
1
>>> command = "echo a; echo b"
>>> shlex.split(command);
    ['echo', 'a; echo', 'b']

so, the problem is shlex module do not handle ";"

Tamarah answered 19/7, 2013 at 10:0 Comment(2)
command.split() gives ['echo', 'a;', 'echo', 'b'] and also fails. `Jello
@Alethaalethea is right. Set "shell=True", the first argument of Popen need a command string like "echo a;echo b". and without "shell=True" the first argument of Popen should be a list, like:["echo","a"]Tamarah
R
1

Got errors like when I used capture_output=True

TypeError: __init__() got an unexpected keyword argument 'capture_output'

After made changes like as below and its works fine

import subprocess

command = '''ls'''

result = subprocess.run(command, stdout=subprocess.PIPE,shell=True)

print(result.stdout.splitlines())
Reliquary answered 17/6, 2022 at 4:11 Comment(2)
capture_output was only introduced in Python 3.7, which is quite old already, but apparently yours is older still.Zina
(The shell=True is useless here; ls runs fine without a shell, though nominally you should then pass the command as a list ["ls"].)Zina
P
0
import subprocess
cmd = "vsish -e ls /vmkModules/lsom/disks/  | cut -d '/' -f 1  | while read diskID  ; do echo $diskID; vsish -e cat /vmkModules/lsom/disks/$diskID/virstoStats | grep -iE 'Delete pending |trims currently queued' ;  echo '====================' ;done ;"


def subprocess_cmd(command):
    process = subprocess.Popen(command,stdout=subprocess.PIPE, shell=True)
    proc_stdout = process.communicate()[0].strip()
    for line in proc_stdout.decode().split('\n'):
        print (line)

subprocess_cmd(cmd)
Paratyphoid answered 25/1, 2021 at 15:51 Comment(2)
Welcome to StackOverflow! Please provide an explanation as to how the code you provided answers the original question.Ussery
Run commends with pipe(|)Paratyphoid
P
0

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
Pontonier answered 28/4, 2024 at 12:22 Comment(1)
The boilerplate combo of Popen + communicate is unnecessary bloat. The standard library provides subprocess.run and the legacy subprocess.check_output and subprocess.check_command precisely so you don't need to write out this every time.Zina
H
-1

What worked for me is very similar to what @Nico Schlömer and @bougui did.

But I had to add squared brackets to my commands and add comma in between each command.

import subprocess

command = ["echo a, echo b"]

after Python 3.7:

ret = subprocess.run(command, capture_output=True, shell=True)

before Python 3.7:

ret = subprocess.run(command, stdout=subprocess.PIPE, shell=True)


print(ret.stdout.splitlines())
Hippodrome answered 10/4, 2024 at 9:33 Comment(1)
This exhibits multiple misunderstandings of what the square brackets mean and what shell=True actually does. The comma is not a valid command separator in any standard shell so it's unclear under which circumstances this could ever have worked anywhere.Zina

© 2022 - 2025 — McMap. All rights reserved.