Python Subprocess: Difference between revisions
(Created page with "= 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 s...") |
|||
(4 intermediate revisions by 2 users not shown) | |||
Line 17: | Line 17: | ||
To facilitate the creation of a sequence of arguments from a string, you can import the module <code>shlex</code> to help with that task. | To facilitate the creation of a sequence of arguments from a string, you can import the module <code>shlex</code> to help with that task. | ||
<source lang="python"> import shlex | <source lang="python"> | ||
>>> import shlex | |||
>>> shlex.split('echo "Hello Process!" ') | |||
['echo', 'Hello Process!']</source> | ['echo', 'Hello Process!']</source> | ||
= Convenience Functions = | = Convenience Functions = | ||
Line 26: | Line 27: | ||
Runs a command with arguments, wait for it to complete, then returns the returncode. | Runs a command with arguments, wait for it to complete, then returns the returncode. | ||
<source lang="python"> subprocess.call(firefox') #launch firefox | <source lang="python"> | ||
>>> subprocess.call(firefox') #launch firefox | |||
>>> subprocess.call(['touch', 'foo'], shell=False) #touch file foo in the current dir | |||
0 | 0 | ||
# same call could performed with Popen | # same call could performed with Popen | ||
>>> subprocess.Popen(firefox') | |||
>>> subprocess.Popen(['touch', 'foo'], shell=False)</source> | |||
== check_output == | == check_output == | ||
Run command with arguments and return its output as a string. | Run command with arguments and return its output as a string. | ||
<source lang="python"> | <source lang="python"> | ||
>>> cmd = shlex.split('figlet "hello process"') #ascii art | |||
>>> p=subprocess.check_output(cmd) | |||
> " _ _ _ \n| |__ ___| | | ___ _ __ _ __ ___ ___ ___ ___ ___ \n| '_ \\ / _ \\ | |/ _ \\ | '_ \\| '__/ _ \\ / __/ _ \\/ __/ __|\n| | | | __/ | | (_) | | |_) | | | (_) | (_| __/\\__ \\__ \\\n|_| |_|\\___|_|_|\\___/ | .__/|_| \\___/ \\___\\___||___/___/\n |_| \n"</source> | > " _ _ _ \n| |__ ___| | | ___ _ __ _ __ ___ ___ ___ ___ ___ \n| '_ \\ / _ \\ | |/ _ \\ | '_ \\| '__/ _ \\ / __/ _ \\/ __/ __|\n| | | | __/ | | (_) | | |_) | | | (_) | (_| __/\\__ \\__ \\\n|_| |_|\\___|_|_|\\___/ | .__/|_| \\___/ \\___\\___||___/___/\n |_| \n"</source> | ||
= Popen = | = Popen = | ||
Line 44: | Line 48: | ||
== Using Popen == | == Using Popen == | ||
=== Execute process === | |||
=== | <source lang="python"> | ||
>>> cmd = shlex.split('ls -trl') | |||
>>> p=subprocess.Popen(cmd)</source> | |||
=== getting subprocess stdout value === | === getting subprocess stdout value === | ||
<source lang="python"> p=subprocess.Popen('ls ~/Downloads', shell=True, stdout=subprocess.PIPE) | <source lang="python"> | ||
>>> 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)</source> | ("7reader_Wikipedia.pdf\nbin-bash_004.png\ncomputerlib.jpg\nDeutsch-Gothic\nDeutsch-Gothic.zip\n, None)</source> | ||
=== piping stdout to another process === | === piping stdout to another process === | ||
Line 59: | Line 65: | ||
# the goal is to replace every 'e' resulting from echo stdout by an '3' | # the goal is to replace every 'e' resulting from echo stdout by an '3' | ||
# and getting the output | # 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) | ('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)</source> | |||
* <code>stdout=subprocess.PIPE</code> - open pipe to stdout of p1 | * <code>stdout=subprocess.PIPE</code> - open pipe to stdout of p1 | ||
* <code>stdin=p1.stdout</code> - the stdout from p1 subprocess is the stdin of p2 | * <code>stdin=p1.stdout</code> - the stdout from p1 subprocess is the stdin of p2 | ||
Line 84: | Line 90: | ||
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 | 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 | ||
<source lang="python"># echo "hi" pipe it to figlet and write the result to foo.txt file | <source lang="python"> | ||
# 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></source> | Out[75]: <subprocess.Popen at 0x7fb5f797d810></source> | ||
On Unix-based system subprocess uses /bin/sh shell. To change the default shell you can set the argument <code>shell=True</code> to define the shell with the <code>executable</code> argument | On Unix-based system subprocess uses /bin/sh shell. To change the default shell you can set the argument <code>shell=True</code> to define the shell with the <code>executable</code> argument | ||
<source lang="python">subprocess.Popen('echo "Hello world!"', shell=True, executable="/bin/bash")</source> | <source lang="python"> | ||
>>> subprocess.Popen('echo "Hello world!"', shell=True, executable="/bin/bash")</source> | |||
<code> | <code> | ||
Hello world! | Hello world! | ||
Line 98: | Line 107: | ||
<source lang="python"> | <source lang="python"> | ||
p=subprocess.Popen("echo 'Hello ee'|sed 's/e/3/g'", shell=True, stdout=subprocess.PIPE) | >>> p=subprocess.Popen("echo 'Hello ee'|sed 's/e/3/g'", shell=True, stdout=subprocess.PIPE) | ||
p.communicate() #read the output from the pipe w/ the communicate() method | >>> p.communicate() #read the output from the pipe w/ the communicate() method | ||
</source> | </source> | ||
Line 110: | Line 119: | ||
<source lang="python"> | <source lang="python"> | ||
p = subprocess.Popen('figlet', stdin=subprocess.PIPE) | >>> p = subprocess.Popen('figlet', stdin=subprocess.PIPE) | ||
myvar = "foo" | >>> myvar = "foo" | ||
p.communicate(myvar) | >>> p.communicate(myvar) | ||
</source> | </source> | ||
<code> | <code> | ||
Line 126: | Line 135: | ||
<source lang="python"> | <source lang="python"> | ||
p = subprocess.Popen(shlex.split("sed 's/e/3/g'"), stdin=subprocess.PIPE, stdout=subprocess.PIPE) | >>> p = subprocess.Popen(shlex.split("sed 's/e/3/g'"), stdin=subprocess.PIPE, stdout=subprocess.PIPE) | ||
c = p.communicate("bee") | >>> c = p.communicate("bee") | ||
print c | >>> print c | ||
</source> | </source> | ||
<code>('b33', None)</code> | |||
== stderr == | == stderr == | ||
stderr consist on the stream of error messages from a program. | stderr consist on the stream of error messages from a program. | ||
<source lang="python"> | <source lang="python"> | ||
p=subprocess.Popen("echo 'Hello ee'|sedx 's/e/3/g'", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) #will create error: there is no sedx application | >>> p=subprocess.Popen("echo 'Hello ee'|sedx 's/e/3/g'", shell=True, stdout=subprocess.PIPE, >>> stderr=subprocess.PIPE) #will create error: there is no sedx application | ||
>>> p.communicate() | |||
</source> | </source> | ||
('', '/bin/sh: 1: sedx: not found\n') | ('', '/bin/sh: 1: sedx: not found\n') | ||
Line 152: | Line 160: | ||
If for instances you want to run a child process that takes time, such adding a book to [http://calibre-ebook.com/ Calibre]'s library, '''the parent process (script) does not wait for the child process to ends''', as in the example below. | If for instances you want to run a child process that takes time, such adding a book to [http://calibre-ebook.com/ Calibre]'s library, '''the parent process (script) does not wait for the child process to ends''', as in the example below. | ||
<source lang="python">#!/usr/bin/env python | <source lang="python"> | ||
#!/usr/bin/env python | |||
import subprocess, shlex | import subprocess, shlex | ||
book = ("/home/andre/Downloads/XML-Wrox.epub") | book = ("/home/andre/Downloads/XML-Wrox.epub") | ||
Line 166: | Line 173: | ||
<code>$ python addbook.py</code> | <code>$ python addbook.py</code> | ||
<source lang=" | <source lang="text"> | ||
PID TTY TIME CMD | |||
16246 pts/11 00:00:00 bash | 16246 pts/11 00:00:00 bash | ||
18667 pts/11 00:00:00 python | 18667 pts/11 00:00:00 python | ||
Line 181: | Line 189: | ||
<source lang="python">#!/usr/bin/env python | <source lang="python">#!/usr/bin/env python | ||
# -*- coding:utf-8 -*- | # -*- coding:utf-8 -*- | ||
import subprocess, shlex | import subprocess, shlex | ||
book = ("/home/andre/Downloads/XML-Wrox.epub") | book = ("/home/andre/Downloads/XML-Wrox.epub") | ||
Line 195: | Line 202: | ||
<code>$ python addbook.py</code> | <code>$ python addbook.py</code> | ||
<source lang=" | <source lang="text">Backing up metadata | ||
Added book ids: 1189 | Added book ids: 1189 | ||
Notifying calibre of the change | Notifying calibre of the change | ||
Line 202: | Line 209: | ||
19557 pts/11 00:00:00 python | 19557 pts/11 00:00:00 python | ||
19570 pts/11 00:00:00 ps</source> | 19570 pts/11 00:00:00 ps</source> | ||
Notice the <code>addbook.wait()</code> forced the script to wait until the addbook subprocess was completed. | Notice the <code>addbook.wait()</code> forced the script to wait until the addbook subprocess was completed. | ||
subprocess- | = 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. | |||
<source lang="python"> | |||
#!/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) | |||
</source> | |||
[[Category: Python]] [[Category: Subprocess]] [[Category: Cookbook]] |
Latest revision as of 11:57, 23 October 2018
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 p1stdin=p1.stdout
- the stdout from p1 subprocess is the stdin of p2stdout=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("echo 'Hello ee'|sed 's/e/3/g'", 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 = "foo"
>>> p.communicate(myvar)
_ _
| |__ (_)
| '_ \| |
| | | | |
|_| |_|_|
(None, None)
Both stdin and stdout pipes can be open in the same subprocess
>>> p = subprocess.Popen(shlex.split("sed 's/e/3/g'"), stdin=subprocess.PIPE, stdout=subprocess.PIPE)
>>> c = p.communicate("bee")
>>> print c
('b33', None)
stderr
stderr consist on the stream of error messages from a program.
>>> p=subprocess.Popen("echo 'Hello ee'|sedx 's/e/3/g'", 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 = ("/home/andre/Downloads/XML-Wrox.epub")
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 = ("/home/andre/Downloads/XML-Wrox.epub")
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)