Python Subprocess: Difference between revisions

From XPUB & Lens-Based wiki
(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...")
 
No edit summary
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">     
    shlex.split('echo &quot;Hello Process!&quot; ')
>>> import shlex
>>> shlex.split('echo &quot;Hello Process!&quot; ')
['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(['touch', 'foo'], shell=False) #touch file foo in the current dir
>>> 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(firefox')
    subprocess.Popen(['touch', 'foo'], shell=False)</source>
>>> 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">   cmd = shlex.split('figlet &quot;hello process&quot;') #ascii art
<source lang="python">
    p=subprocess.check_output(cmd)
>>> cmd = shlex.split('figlet &quot;hello process&quot;') #ascii art
>>> p=subprocess.check_output(cmd)
&gt; &quot; _          _ _                                          \n| |__  ___| | | ___    _ __  _ __ ___  ___ ___  ___ ___ \n| '_ \\ / _ \\ | |/ _ \\  | '_ \\| '__/ _ \\ / __/ _ \\/ __/ __|\n| | | |  __/ | | (_) | | |_) | | | (_) | (_|  __/\\__ \\__ \\\n|_| |_|\\___|_|_|\\___/  | .__/|_|  \\___/ \\___\\___||___/___/\n                      |_|                                \n&quot;</source>
&gt; &quot; _          _ _                                          \n| |__  ___| | | ___    _ __  _ __ ___  ___ ___  ___ ___ \n| '_ \\ / _ \\ | |/ _ \\  | '_ \\| '__/ _ \\ / __/ _ \\/ __/ __|\n| | | |  __/ | | (_) | | |_) | | | (_) | (_|  __/\\__ \\__ \\\n|_| |_|\\___|_|_|\\___/  | .__/|_|  \\___/ \\___\\___||___/___/\n                      |_|                                \n&quot;</source>
= Popen =
= Popen =
Line 44: Line 48:


== Using Popen ==
== Using Popen ==
=== Execute process ===


=== Execute process ===
<source lang="python">
>>> cmd = shlex.split('ls -trl')
>>> p=subprocess.Popen(cmd)</source>


<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">     
    p1.communicate()
>>> p=subprocess.Popen('ls ~/Downloads', shell=True, stdout=subprocess.PIPE)  
>>> p1.communicate()
(&quot;7reader_Wikipedia.pdf\nbin-bash_004.png\ncomputerlib.jpg\nDeutsch-Gothic\nDeutsch-Gothic.zip\n, None)</source>
(&quot;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(&quot;echo Hello e | sed 's/e/3/g'&quot;, shell=True, stdout=subprocess.PIPE)
>>> p=subprocess.Popen(&quot;echo Hello e | sed 's/e/3/g'&quot;, shell=True, stdout=subprocess.PIPE)
    p.communicate()
>>> p.communicate()
('H3llo 3\n', None)
('H3llo 3\n', None)


>>> cmd = shlex.split('echo &quot;hello process&quot;')
>>> cmd_sed = shlex.split(&quot;sed 's/e/3/g'&quot;)
>>> 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>


    cmd = shlex.split('echo &quot;hello process&quot;')
    cmd_sed = shlex.split(&quot;sed 's/e/3/g'&quot;)
    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 &quot;hi&quot; pipe it to figlet and write the result to foo.txt file
<source lang="python">
    subprocess.Popen(&quot;echo hi | figlet &gt; foo.txt&quot;, shell=True)
# echo &quot;hi&quot; pipe it to figlet and write the result to foo.txt file
>>> subprocess.Popen(&quot;echo hi | figlet &gt; foo.txt&quot;, shell=True)
Out[75]: &lt;subprocess.Popen at 0x7fb5f797d810&gt;</source>
Out[75]: &lt;subprocess.Popen at 0x7fb5f797d810&gt;</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 &quot;Hello world!&quot;', shell=True, executable=&quot;/bin/bash&quot;)</source>
<source lang="python">
>>> subprocess.Popen('echo &quot;Hello world!&quot;', shell=True, executable=&quot;/bin/bash&quot;)</source>
<code>
<code>
Hello world!
Hello world!
Line 98: Line 107:


<source lang="python">     
<source lang="python">     
p=subprocess.Popen(&quot;echo 'Hello ee'|sed 's/e/3/g'&quot;, shell=True, 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
>>> 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 = &quot;foo&quot;
>>> myvar = &quot;foo&quot;
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(&quot;sed 's/e/3/g'&quot;), stdin=subprocess.PIPE, stdout=subprocess.PIPE)
>>> p = subprocess.Popen(shlex.split(&quot;sed 's/e/3/g'&quot;), stdin=subprocess.PIPE, stdout=subprocess.PIPE)
c = p.communicate(&quot;bee&quot;)
>>> c = p.communicate(&quot;bee&quot;)
print c
>>> print c
</source>
</source>
('b33', None)
<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(&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=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()
>>> 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">
# -*- coding:utf-8 -*-
#!/usr/bin/env python
 
import subprocess, shlex  
import subprocess, shlex  
book = (&quot;/home/andre/Downloads/XML-Wrox.epub&quot;)
book = (&quot;/home/andre/Downloads/XML-Wrox.epub&quot;)
Line 166: Line 173:
<code>$ python addbook.py</code>
<code>$ python addbook.py</code>


<source lang="python">PID TTY          TIME CMD
<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 = (&quot;/home/andre/Downloads/XML-Wrox.epub&quot;)
book = (&quot;/home/andre/Downloads/XML-Wrox.epub&quot;)
Line 195: Line 202:
<code>$ python addbook.py</code>
<code>$ python addbook.py</code>


<source lang="python">Backing up metadata
<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-tutorial.wiki (END)

Revision as of 10:03, 22 May 2015

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 &quot;Hello Process!&quot; ')
['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 &quot;hello process&quot;') #ascii art
>>> p=subprocess.check_output(cmd)
&gt; &quot; _          _ _                                           \n| |__   ___| | | ___    _ __  _ __ ___   ___ ___  ___ ___ \n| '_ \\ / _ \\ | |/ _ \\  | '_ \\| '__/ _ \\ / __/ _ \\/ __/ __|\n| | | |  __/ | | (_) | | |_) | | | (_) | (_|  __/\\__ \\__ \\\n|_| |_|\\___|_|_|\\___/  | .__/|_|  \\___/ \\___\\___||___/___/\n                       |_|                                \n&quot;

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()
(&quot;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(&quot;echo Hello e | sed 's/e/3/g'&quot;, shell=True, stdout=subprocess.PIPE)
>>> p.communicate()
('H3llo 3\n', None)

>>> cmd = shlex.split('echo &quot;hello process&quot;')
>>> cmd_sed = shlex.split(&quot;sed 's/e/3/g'&quot;)
>>> 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 &quot;hi&quot; pipe it to figlet and write the result to foo.txt file
>>> subprocess.Popen(&quot;echo hi | figlet &gt; foo.txt&quot;, shell=True)
Out[75]: &lt;subprocess.Popen at 0x7fb5f797d810&gt;

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 &quot;Hello world!&quot;', shell=True, executable=&quot;/bin/bash&quot;)

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.