how to execute a bash command in a python script

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

  • Anthon

    Anthon Correct answer

    6 years ago

    @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()
    

    Is it recommended to use (`os.popen` or `os.system`), ex: `res = os.popen('sudo apt update').read()`? @Anthon

    No. If you cannot use `plumbum`, have a look at `subprocess.run()` and use that

    I am using long complicated shell commands, hence using under `os.system()` is pretty easy, where I just need to give command line as input string. Why is it not recommend to be used? @Anthon

    It starts an extra shell just to execute the command, you don't get any feedback from `os.system()`.

    How about following example: `os.popen('my_command').read()` which returns an output. But as I understand extra shell consumes additional time. @Anthob

    @alper Read 0324 https://www.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.*

    Could I use as with `shell=True` option is it recommended way to use, example: `subprocess.check_output('sudo apt update', shell=True).strip().decode('utf-8')` @Anthon

    @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).

    I will use it without providing `sudo` for general commands. I have to use `|` (pipe) on my commands, which I cannot able to use it via `subprocess.run()`. For example command(`echo hello | cut -c 2-`) => `res = subprocess.check_output(["echo", "hello", "|", "cut -c 2-"])` does not see `|`. If you could extend your answer for pipe `|` usage under `subprocess ` that will be so helpful.

    @alper in that case take a serious look at plumbum, it is worth getting up to speed with that.

    `subprocess.check_output()` returns byte stream (`b''`), I guess we need to add `.decode('utf-8').strip()` to end of `subprocess.check_output(["sudo", "apt", "update"])` to convert it into character stream. Instead of doing this additional conversion, could we use `subprocess.Popen` with `universal_newlines=True` flag: `res = subprocess.Popen(['sudo', 'apt', 'update'], stdout=subprocess.PIPE, universal_newlines=True).communicate()[0].strip()` to increase its speed?

    @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.

License under CC-BY-SA with attribution


Content dated before 6/26/2020 9:53 AM