21

How can I launch a bash command with multiple args (for example "sudo apt update") from a python script?

1

8 Answers 8

23

@milne's answer works, but subprocess.call() gives you little feedback.

I prefer to use subprocess.check_output() so you can analyse what was printed to stdout:

 import subprocess res = subprocess.check_output(["sudo", "apt", "update"]) for line in res.splitlines(): # process the output line by line 

check_output throws an error on on-zero exit of the invoked command

Please note that this doesn't invoke bash or another shell if you don't specify the shell keyword argument to the function (the same is true for subprocess.call(), and you shouldn't if not necessary as it imposes a security hazard), it directly invokes the command.

If you find yourself doing a lot of (different) command invocations from Python, you might want to look at plumbum. With that you can do the (IMO) more readable:

from plumbum.cmd import sudo, apt, echo, cut res = sudo[apt["update"]]() chain = echo["hello"] | cut["-c", "2-"] chain() 
12
  • Is it recommended to use (os.popen or os.system), ex: res = os.popen('sudo apt update').read()? @Anthon
    – alper
    CommentedAug 8, 2018 at 13:48
  • 2
    @alper Read 0324 python.org/dev/peps/pep-0324. That explains the rationale for making subprocess although os.system and os.popen already existed. Such PEPs are non-trivial to get accepted. Several people have given much more thought to that than you or I ever will. And subprocess has improved since 2003, the others are just still there for backward compatibility. Have you red the os.system manual page: The subprocess module provides more powerful facilities for spawning new processes and retrieving their results; using that module is preferable to using this function.
    – Anthon
    CommentedAug 8, 2018 at 18:58
  • 1
    @alper Yes you could. As I indicated that is a potential security hazard, so I don't know why you think that is recommended. And invoking sudo is only going to make that more severe. Maybe using python-apt is a better solution (I have not looked into that myself).
    – Anthon
    CommentedAug 31, 2018 at 13:36
  • 1
    @alper in that case take a serious look at plumbum, it is worth getting up to speed with that.
    – Anthon
    CommentedAug 31, 2018 at 13:50
  • 1
    @alper If you are on Python3 you need to do that, on Python2 you don't. Please don't use the commenting here as a chat system. If you have a question post it as such.
    – Anthon
    CommentedSep 2, 2018 at 17:07
12

It is possible you use the bash as a program, with the parameter -c for execute the commands:

Example:

bashCommand = "sudo apt update" output = subprocess.check_output(['bash','-c', bashCommand]) 
    9

    The subprocess module is designed to do this:

    import subprocess subprocess.call(["sudo", "apt", "update"]) 

    If you would like the script to terminate if the command fails, you might consider using check_call() instead of parsing the return code yourself:

    subprocess.check_call(["sudo", "apt", "update"]) 
    4
    • this gave me the following traceback : Traceback (most recent call last): File "/home/Dremor/test.py", line 3, in <module> subprocess.call('sudo', 'yum', 'update') File "/usr/lib64/python3.4/subprocess.py", line 537, in call with Popen(*popenargs, **kwargs) as p: File "/usr/lib64/python3.4/subprocess.py", line 767, in __init__ raise TypeError("bufsize must be an integer") TypeError: bufsize must be an integer (I'm using yum as I'm using Fedora as main OS)
      – Dremor
      CommentedMar 16, 2015 at 16:07
    • 3
      You forgot the square bracketsCommentedMar 16, 2015 at 16:19
    • 1
      Also note that subprocess.call() is blocking while subprocess.Popen() is non-blocking..
      – heemayl
      CommentedMar 16, 2015 at 18:02
    • What do you mean by "blocking"? @heemayl
      – alper
      CommentedSep 2, 2018 at 17:19
    4

    Also you can use 'os.popen'.

    Example:

    import os command = os.popen('ls -al') print(command.read()) print(command.close()) 

    Output:

    total 16 drwxr-xr-x 2 root root 4096 ago 13 21:53 . drwxr-xr-x 4 root root 4096 ago 13 01:50 .. -rw-r--r-- 1 root root 1278 ago 13 21:12 bot.py -rw-r--r-- 1 root root 77 ago 13 21:53 test.py None 
      1

      For python 3.5 and more you can use:

      import subprocess try: result = subprocess.run("sudo apt update", check=True, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) except subprocess.CalledProcessError as err: raise Exception(str(err.stderr.decode("utf-8"))) except Exception as err: raise Exception(err) else: return result.stdout.decode("utf-8") 
        0

        use subprocess module

        import subprocess command = 'sudo apt update' subprocess.check_call(command.split()) 
        5
        • why am i getting an error when i use itCommentedApr 1, 2019 at 19:52
        • subprocess.check_call(["sudo", "apt", "update"])CommentedApr 1, 2019 at 19:52
        • CalledProcessError Traceback (most recent call last) <ipython-input-21-33ce645708e7> in <module>() ----> 1 subprocess.check_call(["sudo", "apt", "update"]) /usr/lib/python2.7/subprocess.pyc in check_call(*popenargs, **kwargs) 188 if cmd is None: 189 cmd = popenargs[0] --> 190 raise CalledProcessError(retcode, cmd) 191 return 0 192 CalledProcessError: Command '['sudo', 'apt', 'update']' returned non-zero exit status 1CommentedApr 1, 2019 at 19:52
        • this error only happens when i run it in a jupyter notebookCommentedApr 1, 2019 at 19:53
        • maybe i'll start a new question since this is otCommentedApr 1, 2019 at 19:54
        0
        import os os.system("sudo apt update") 
          0

          I like @ricardo130's os.popen() approach because it can handle pipes, redirects, and incremental (realtime) output:

          import os with os.popen("sudo apt update") as stream: for line in stream.readlines(): print(line) 

          And you can do whatever you like with the output before displaying the output. Or use tqdm for a progress bar. To create a table of the largest files in your git history from this answer:

          from tqdm import tqdm import json cmd = """ git rev-list --objects --all \ | git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \ | sed -n 's/^blob //p' \ | sort --numeric-sort --key=2 \ | cut -c 1-12,41- \ | $(command -v gnumfmt || echo numfmt) --field=2 --to=iec-i --suffix=B --padding=7 --round=nearest """ with os.popen(cmd) as stream: table = [] for line in tqdm(stream.readlines()): row = [line[12:20].strip(), line[20:].strip()] table.append(row) print(json.dumps(table, indent=2)) 

          Which outputs something like this:

          ... [ "51MiB", ".nlpia2-data/fake-news-dataset-true.csv" ], [ "60MiB", ".nlpia2-data/fake-news-dataset-fake.csv" ] ] 

            You must log in to answer this question.

            Start asking to get answers

            Find the answer to your question by asking.

            Ask question

            Explore related questions

            See similar questions with these tags.