Python Subprocess

From Media Design: Networked & Lens-Based wiki
Jump to navigation Jump to search

Suprocess

Although subprosses in Python are incredible useful and I use them often to interact with command-line applications, I often fail to know exactly which of the subprocess methods to use in each situation. I'll try to make it more understandable to myself and hopefully to you in this extended recipe.

A lot of the information collected here comes from http://jimmyg.org/blog/2009/working-with-python-subprocess.html , which offers a great introduction to both command-line and suprocess module. As well as from Python 2.7 official documentation on subprocess, from which some chunks of text were copied from.

To start working with the subprocesses import the module. import subprocess

Suprocess can be used in two ways: * using convenience functions: call, check_call and check_output * or Popen interface, which allows a great customization, being able to replicate the behavior of any of the convenience functions.

subprocess args

In a subprocess args are the heart of the call. They are essential the command you'd run in shell, but now you are using Python to launch them.

Args can either be a string of a sequence of arguments. A sequence of arguments is generally preferred, as it allows the module to take care of any required escaping and quoting of arguments (e.g. to permit spaces in file names).

To facilitate the creation of a sequence of arguments from a string, you can import the module shlex to help with that task.

    
>>> import shlex
>>> shlex.split('echo "Hello Process!" ')
['echo', 'Hello Process!']

Convenience Functions

subprocess.call

Runs a command with arguments, wait for it to complete, then returns the returncode.

    
>>> subprocess.call(firefox') #launch firefox
>>> subprocess.call(['touch', 'foo'], shell=False) #touch file foo in the current dir
0
# same call could performed with Popen
>>> subprocess.Popen(firefox')
>>> subprocess.Popen(['touch', 'foo'], shell=False)

check_output

Run command with arguments and return its output as a string.

>>> cmd = shlex.split('figlet "hello process"') #ascii art
>>> p=subprocess.check_output(cmd)
> " _          _ _                                           \n| |__   ___| | | ___    _ __  _ __ ___   ___ ___  ___ ___ \n| '_ \\ / _ \\ | |/ _ \\  | '_ \\| '__/ _ \\ / __/ _ \\/ __/ __|\n| | | |  __/ | | (_) | | |_) | | | (_) | (_|  __/\\__ \\__ \\\n|_| |_|\\___|_|_|\\___/  | .__/|_|  \\___/ \\___\\___||___/___/\n                       |_|                                \n"

Popen

The Popen class offers the flexibility to handle less common cases not covered by the convenience functions.

Using Popen

Execute process

>>> cmd = shlex.split('ls -trl')
>>> p=subprocess.Popen(cmd)

getting subprocess stdout value

    
>>> p=subprocess.Popen('ls ~/Downloads', shell=True, stdout=subprocess.PIPE) 
>>> p1.communicate()
("7reader_Wikipedia.pdf\nbin-bash_004.png\ncomputerlib.jpg\nDeutsch-Gothic\nDeutsch-Gothic.zip\n, None)

piping stdout to another process

# in this example will be piping the stdout of echo to the stdin of sed
# the goal is to replace every 'e' resulting from echo stdout by an '3'
# and getting the output
>>> p=subprocess.Popen("echo Hello e | sed 's/e/3/g'", shell=True, stdout=subprocess.PIPE)
>>> p.communicate()
('H3llo 3\n', None)

>>> cmd = shlex.split('echo "hello process"')
>>> cmd_sed = shlex.split("sed 's/e/3/g'")
>>> p1 = subprocess.Popen(cmd, stdout=subprocess.PIPE)
>>> p2 = subprocess.Popen(cmd_sed, stdin=p1.stdout,stdout=subprocess.PIPE)
>>> p2.communicate()
('h3llo proc3ss\n', None)
  • stdout=subprocess.PIPE - open pipe to stdout of p1
  • stdin=p1.stdout - the stdout from p1 subprocess is the stdin of p2
  • stdout=subprocess.PIPE- open pipe to stdout of p2

Popen options - explained

stdin, stdout and stderr

stdin, stdout and stderr specify the executed program’s standard input, standard output and standard error file handles, respectively.

shell True

If shell is True, the specified command will be executed through the shell. It offers access to shell features such as shell pipes, filename wildcards, environment variable expansion

# echo "hi" pipe it to figlet and write the result to foo.txt file
>>> subprocess.Popen("echo hi | figlet > foo.txt", shell=True)
Out[75]: <subprocess.Popen at 0x7fb5f797d810>

On Unix-based system subprocess uses /bin/sh shell. To change the default shell you can set the argument shell=True to define the shell with the executable argument

>>> subprocess.Popen('echo "Hello world!"', shell=True, executable="/bin/bash")

Hello world! <subprocess.Popen object at 0x...>

stdout

stdout is the output that is written by a shell program to the terminal emulator. That is why you are able to see the outcome of the ran commands. If you want to read the output inside your Python script, and maybe store it in a variable, you need to set the stdout argument in the initial call to Popen, specifying that a pipe to the standard stream should be opened: stdout=subprocess.PIPE

    
>>> p=subprocess.Popen(&quot;echo 'Hello ee'|sed 's/e/3/g'&quot;, shell=True, stdout=subprocess.PIPE)
>>> p.communicate() #read the output from the pipe w/ the communicate() method

('H3llo 33\n', None) The communicate() method returns a tuple with two values, containing the data from stdout and the data from stderr two values.

stdin

stdin is the stream of data (often text) that is written to the program; As "Hello World" is written to echo in echo "Hello World".
In order to write to shell program you have to open a pipe to stdin. stdin=subprocess.PIPE

     
>>> p = subprocess.Popen('figlet', stdin=subprocess.PIPE)
>>> myvar = &quot;foo&quot;
>>> p.communicate(myvar)

_ _ | |__ (_) | '_ \| | | | | | | |_| |_|_| (None, None)

Both stdin and stdout pipes can be open in the same subprocess

    
>>> p = subprocess.Popen(shlex.split(&quot;sed 's/e/3/g'&quot;), stdin=subprocess.PIPE, stdout=subprocess.PIPE)
>>> c = p.communicate(&quot;bee&quot;)
>>> print c

('b33', None)

stderr

stderr consist on the stream of error messages from a program.

    
>>> p=subprocess.Popen(&quot;echo 'Hello ee'|sedx 's/e/3/g'&quot;, shell=True, stdout=subprocess.PIPE, >>> stderr=subprocess.PIPE) #will create error: there is no sedx application
>>> p.communicate()
(, '/bin/sh: 1: sedx: not found\n')

wait and poll

When you launch a subprocess, the parent process - the python script - doesn't wait for the child processes to finish execution.

If you want the script to wait for the termination of the subprocess, you need to rely on poll() or wait(), to return the value 0, indicating the exit status.

Popen.poll() Check if child process has terminated. Popen.wait() Wait for child process to terminate.

Examples

If for instances you want to run a child process that takes time, such adding a book to Calibre's library, the parent process (script) does not wait for the child process to ends, as in the example below.

#!/usr/bin/env python
import subprocess, shlex 
book = (&quot;/home/andre/Downloads/XML-Wrox.epub&quot;)

cmd_addbook = shlex.split('calibredb add --library=/home/andre/CalibreLibrary {var_book}'.format(var_book=book))
addbook = subprocess.Popen(cmd_addbook) #add book to calibre library

ps = subprocess.Popen('ps', stdout=subprocess.PIPE) #list the running processes
ps_output = ps.communicate()[0]
print ps_output

$ python addbook.py

PID TTY          TIME CMD
16246 pts/11   00:00:00 bash
18667 pts/11   00:00:00 python
18668 pts/11   00:00:00 calibredb
18669 pts/11   00:00:00 ps

Backing up metadata
Added book ids: 1188
Notifying calibre of the change

Notice that ps_output is printed before calibredb has finnished its process - adding the book and calibredb;

If you want ps to wait until the previous subprocess is finnished , you can use Popen.wait().

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import subprocess, shlex 
book = (&quot;/home/andre/Downloads/XML-Wrox.epub&quot;)

cmd_addbook = shlex.split('calibredb add --library=/home/andre/CalibreLibrary {var_book}'.format(var_book=book))
addbook = subprocess.Popen(cmd_addbook) #add book to calibre library

addbook.wait() # WAIT for addbook subprocess to be completed

ps = subprocess.Popen('ps', stdout=subprocess.PIPE) #list the running processes
ps_output = ps.communicate()[0]
print ps_output

$ python addbook.py

Backing up metadata
Added book ids: 1189
Notifying calibre of the change
PID TTY          TIME CMD
16246 pts/11   00:00:00 bash
19557 pts/11   00:00:00 python
19570 pts/11   00:00:00 ps

Notice the addbook.wait() forced the script to wait until the addbook subprocess was completed.

Recipes

Reading vu data from aplay

"Realtime" information such as the vumeter from aplay uses the special "\r" newline to make a line that continually updates itself (rather than visibly dumping all the lines consecutively). Use the "universal_newlines" option makes the readline() function break up these lines as well.

#!/usr/bin/env python3

import subprocess, argparse

ap = argparse.ArgumentParser()
ap.add_argument("input")
args = ap.parse_args()

p = subprocess.Popen(["aplay", args.input, "--vumeter=mono"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True)
while True:
    data = p.stdout.readline()
    if not data:
        break
    data = data.rstrip()
    if data.endswith("%"):
        vu = int(data[:-1][-3:])
        print ("*", vu)