Developers many times need to execute programs that are installed on the operating system but are not available through any Python module. They also need to capture their results (output, errors, etc). Many of these programs are easily executable as a command from the shell in Unix/Linux and command prompt on windows. Python provides a module named subprocess which lets us execute these programs by creating an independent child process. It also lets us capture their results. The subprocess module gives full control over program execution. It lets us create a process, capture results, capture errors, communicate with the process and terminate/kill the process if it does not complete within the expected time.
The subprocess module lets us create a child process in two ways using its API.
The parameters of run() and Popen() are almost same. The Popen() is very comprehensive and should be only for advanced cases.
As a part of this tutorial, we'll explain how we can create a process to execute a program in Python using subprocess module with very simple and easy-to-understand examples.
As a part of our first example, we'll create a simple child process using run() method that executes the Linux ls command and returns the result (list of files/directories in the present directory). We can provide commands as a list of strings to run() method.
Our code for this example creates 3 subprocesses each of which executes the same command. Our first call to run() method is without any parameters other than a program to execute. It does not capture any results of the execution of ls command as a process. Our second call to run() method sets capture_output parameter asking it to capture results of execution of program. Our third call to run() method sets stdout and stderr parameters to capture results and errors during execution of program.
We can notice from the output of a call to run() that it returns an instance of CompletedProcess. We have printed output captures in second and third call to run() method using stdout and strerr attributes of CompletedProcess instance. The output returned by the execution is in bytes format hence we are calling decode() method on them to decode output. We are only printing the first 5 lines of output to avoid a lot of output as the current directory has a lot of files. It’s not always necessary to capture output though.
The stdout and stderr parameter also accepts value PIPE which is special value available from subprocess module and creates io stream to capture output and errors.
import subprocess
print("========= Run 1 =======================")
res = subprocess.run(["ls"])
print("\nResult Type : {}".format(res))
print("Return Code : {}".format(res.returncode))
print("Result Output Stream : {}".format(res.stdout if res.stdout else "No Result"))
print("Result Error Stream : {}\n".format(res.stderr if res.stderr else "No Error"))
print("========= Run 2 =======================")
res = subprocess.run(["ls"], capture_output=True)
result_lines = res.stdout.decode().split("\n")
print("\nReturn Code : {}".format(res.returncode))
print("\nResult Output Stream :")
for line in result_lines[:5]:
print(line)
print("\nResult Error Stream : {}\n".format(res.stderr.decode() if res.stderr else "No Error"))
print("========= Run 3 =======================")
res = subprocess.run(["ls"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
result_lines = res.stdout.decode().split("\n")
print("\nReturn Code : {}".format(res.returncode))
print("\nResult Output Stream : ")
for line in result_lines[:5]:
print(line)
print("\nResult Error Stream : {}\n".format(res.stderr.decode() if res.stderr else "No Error"))
As a part of our second example, we are again building on the first example. We are again executing ls command as a child process using run() method but this time introducing more parameters of the method for an explanation.
The first part of the code creates a child process that executes ls command. We have given encoding and errors parameters as new parameters to the method. The second part of the code again executes ls command but explains the usage of text parameter. The third part of the example executes ls command with argument --a which is the wrong argument and it'll fail the command. We have created this example to explain how we can capture errors that occurred during the child process.
Please make a note that we have not used decode() method as a part of these examples because all of them return string output due to their parameters, unlike the previous example.
The output of our first and second parts of the code is exactly the same as in our previous example. The output of the third part of the code successfully captures the error of the child process.
print("========= Run 1 =======================")
res = subprocess.run(["ls"], capture_output=True, encoding="utf", errors="ignore")
result_lines = res.stdout.split("\n")
print("\nReturn Code : {}".format(res.returncode))
print("\nResult Output Stream : ")
for line in result_lines[:5]:
print(line)
print("\nResult Error Stream : {}\n".format(res.stderr if res.stderr else "No Error"))
print("========= Run 2 =======================")
res = subprocess.run(["ls"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
result_lines = res.stdout.split("\n")
print("\nReturn Code : {}".format(res.returncode))
print("\nResult Output Stream : ")
for line in result_lines[:5]:
print(line)
print("\nResult Error Stream : {}\n".format(res.stderr if res.stderr else "No Error"))
print("========= Run 3 =======================")
res = subprocess.run(["ls", "--a"], capture_output=True, encoding="utf")
print("\nReturn Code : {}".format(res.returncode))
print("\nResult Output Stream : {}".format(res.stdout if res.stdout else "No Result"))
print("\nResult Error Stream : {}".format(res.stderr))
We are again using our third example to demonstrate some important parameters of the run() method.
Our code for this example has two parts. Both parts execute a command passed to it in the shell as shell command is set to True. We even have passed command as a single string rather than a list of strings because shell parameter is set to True. The first part of the code executes ls -l command which lists files and directories in the present directory. The second part of the code executes the command ls -l | grep .py which lists down the list of files and directories which are ending with .py extension.
print("========= Run 1 =======================")
res = subprocess.run("ls -l", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, text=True)
result_lines = res.stdout.split("\n")
print("\nReturn Code : {}".format(res.returncode))
print("\nResult Output Stream : ")
for line in result_lines[:5]:
print(line)
print("\nResult Error Stream : {}\n".format(res.stderr if res.stderr else "No Error"))
print("========= Run 2 =======================")
res = subprocess.run("ls -l | grep .py", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, text=True)
result_lines = res.stdout.split("\n")
print("\nReturn Code : {}".format(res.returncode))
print("\nResult Output Stream : ")
for line in result_lines[:5]:
print(line)
print("\nResult Error Stream : {}\n".format(res.stderr if res.stderr else "No Error"))
As a part of our fourth example, we are executing a Python script as an independent process using run() method. We are capturing its output as well. We'll be reusing this script in future examples as well for demonstration.
The script has a function named addition which accepts two parameters and returns their sum. It waits for 2 seconds before returning output to mimic a real-life situation that computing sum takes time. We are then calling this function four times in the script. The first three calls are all with integer argument hence completes fine. The last call has one argument as a string hence fails with an error. We have captured the error using stderr parameter. We are then printing both normal output and errors that were captured when running the script as a child process.
Please make a NOTE that the process returns code 1 as there were errors during execution.
addition.py
import time
def addition(a, b):
time.sleep(2)
return a + b
if __name__ == "__main__":
res = addition(10,20)
print("{} + {} = {}".format(10,20, res))
res = addition(20,20)
print("{} + {} = {}".format(20,20, res))
res = addition(20,30)
print("{} + {} = {}".format(20,30, res))
res = addition('20',30)
print("{} + {} = {}".format('20',30, res))
res = subprocess.run(["python", "addition.py"], capture_output=True, encoding="utf")
result_lines = res.stdout.split("\n")
print("Return Code : {}".format(res.returncode))
print("\nResult Output Stream : ")
for line in result_lines:
print(line)
print("\nResult Error Stream : ")
print(res.stderr if res.stderr else "No Error")
We are using our fifth example to explain how we can direct standard errors to standard output. Our code for this example is exactly the same as our previous example with minor changes. We have set stdout to PIPE and stderr to STDOUT. The STDOUT value will inform run() method that directs the error captured as a part of the standard error to the standard output stream.
We can notice from the output captured that the error is now getting printed when printing the standard output stream.
res = subprocess.run(["python", "addition.py"],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
encoding="utf")
result_lines = res.stdout.split("\n")
print("Return Code : {}".format(res.returncode))
print("\nResult Output Stream : ")
for line in result_lines:
print(line)
print("\nResult Error Stream : {}".format(res.stderr if res.stderr else "No Error"))
As a part of our sixth example, we'll demonstrate how we can timeout the child process if it’s taking more than expected time.
Our code for this example again runs addition.py script from the previous example but this time it calls method run() with timeout parameter set to 4 seconds. It'll timeout the process if it does not complete within 4 seconds and raise TimeoutExpired error. We have wrapped the code into the try-except block to capture errors.
try:
res = subprocess.run(["python", "addition.py"], capture_output=True, encoding="utf", timeout=4)
except Exception as e:
print("ErrorType : {}, Error : {}".format(type(e).__name__, e))
print("Output : ")
print(e.output, e.stdout)
print("Detailed Error : ")
print(e.stderr)
As a part of our seventh example, we'll explain how we can check for the errors that occurred while executing the child process.
Our code for this example is exactly the same as our previous example with the only change that we have set check parameter to True and timeout parameter is removed. The execution raises CalledProcessError error which we are capturing and printing error details.
We can notice from the output that it also has captured the output of the code which ran successfully.
try:
res = subprocess.run(["python", "addition.py"], capture_output=True, encoding="utf", check=True)
except Exception as e:
print("ErrorType : {}, Error : {}".format(type(e).__name__, e))
print("Return Code : {}".format(e.returncode))
print("Output : ")
print(e.stdout)
print("Detailed Error : ")
print(e.stderr)
We are using our eighth example to demonstrate how we can give input to the child process using input parameter of the run() method. We have created another script for this part of the code.
The script has addition function which is the same as the previous script. The main part of the code asks for the user to enter the value of the parameters of the addition function and then calls the function using these inputs. It does that two times. We are passing data to the child process in two different formats. Once as a string and once as bytes. We have passed values separated by new line characters to indicate that input is completed.
We can notice from the output that it seems to have captured values correctly.
addition_user_input.py
import time
def addition(a, b):
time.sleep(2)
return a + b
if __name__ == "__main__":
a = input("Enter First Number : ")
b = input("Enter Second Number : ")
res = addition(int(a),int(b))
print("\n{} + {} = {}".format(a,b, res))
a = input("Enter First Number : ")
b = input("Enter Second Number : ")
res = addition(int(a),int(b))
print("\n{} + {} = {}".format(a,b, res))
This part of the code calls the script and passes input to the script as a string. We have set encoding to utf which will let us give input in string format. If we have set text to True then also we can give input as a string.
res = subprocess.run(["python", "addition_user_input.py"], input="20\n25\n20\n30\n",
capture_output=True, encoding="utf")
print("Return Code : {}".format(res.returncode))
print("\nResult Output Stream : ")
result_lines = res.stdout.split("\n")
for line in result_lines:
print(line)
print("\nResult Error Stream : {}".format(res.stderr if res.stderr else "No Error"))
This part of the code explains how we can give bytes as input to the child process. We need to give input in bytes format if we have not set test or encoding parameter.
res = subprocess.run(["python", "addition_user_input.py"], input=bytes("20\n25\n20\n30\n", encoding="utf"),
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print("Return Code : {}".format(res.returncode))
print("\nResult Output Stream : ")
result_lines = res.stdout.decode().split("\n")
for line in result_lines:
print(line)
print("\nResult Error Stream : {}".format(res.stderr.decode() if res.stderr else "No Error"))
As a part of our ninth example, we'll demonstrate how we can give environment variables to our child process by setting env parameter of the run() method. We can give different environment variables than the one set by the parent process which started the child process.
We have created a simple script that just gets environment variables by using os module and prints the value of environment variables HOME, USER, and PASS. Our main part of the code calls this script using run() method. It first gets current environment variables mapping and creates a copy of it. It then sets the 3 parameters which we mentioned earlier. It then passes this mapping of environment variables as env parameter of the run() method.
We can notice from the output that it seems to have correctly captured the value of environment variables.
environment_variables.py
import os
def print_env_variables():
for env_var in ["HOME", "USER", "PASS"]:
print("{} : {}".format(env_var, os.environ[env_var]))
print_env_variables()
import os
new_env = os.environ.copy()
new_env["HOME"] = "/home/sunny"
new_env["USER"] = "coderzcolumn"
new_env["PASS"] = "CC"
res = subprocess.run(["python", "environment_variables.py"],
env = new_env,
capture_output=True, encoding="utf")
result_lines = res.stdout.split("\n")
print("Result Output Stream : ")
for line in result_lines[:5]:
print(line)
print("\nResult Error Stream : {}".format(res.stderr if res.stderr else "No Error"))
As a part of our example, we'll explain how we can create a child process using Popen() constructor. The arguments of Popen constructor are almost the same as that of run() method, even it has more parameters than run() method. The majority of our previous examples will work even with Popen constructor as well. The Popen constructor does not have parameters named capture_output, timeout, input, and check, on the other hand, it has other methods to perform the functionalities provided by these parameters.
As we discussed earlier, unlike run() method which waits for the child process to complete, Popen() returns immediately with an instance of type Popen after the process has started. Our code for this part is exactly the same as our previous examples with the only change that we are using Popen() to launch a child process that will run addition.py Python script.
We can notice from the output that it’s almost the same as our previous examples. When we run this example, the Popen() constructor returns immediately. with an instance of Popen instead of waiting for the child process to complete. This is the reason the return code is None because it’s not set yet. It'll be set after the child process has completed.
res = subprocess.Popen(["python", "addition.py"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf")
print("Returned Object Type : {}".format(res))
print("Process ID : {}".format(res.pid))
print("Return Code : {}".format(res.returncode))
print("\nResult Output Stream : ")
for line in res.stdout:
print(line, end="")
print("\nResult Error Stream : ")
for line in res.stderr:
print(line, end="")
As a part of our eleventh example, we'll demonstrate how we can instruct the main process to wait for the child process to complete using wait() method of Popen instance. We'll also explain the usage of the poll() method which can be used to check the completion of the child process.
Our code for this part is almost exactly the same as our previous example with few minor changes. After creating an instance of Popen to run addition.py Python script. We are first calling poll() method to check whether the child process is terminated. We are then calling wait() method asking our main process to wait for the child process to complete. We are then again calling poll() to check for the status of the child process. We have called the wait method without timeout hence it'll wait forever for the child process to complete.
We can notice from the output that the initial return code is None. After the child process completes, we are checking for the return code again and it’s set to 1 after completion (child process had a failure.). When we run the script we can notice from the output that it waits for the child process to complete.
res = subprocess.Popen(["python", "addition.py"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf")
print("Returned Object Type : {}".format(res))
ret_code = res.poll()
print("Return Code Initially : {}".format(ret_code))
res.wait()
ret_code = res.poll()
print("Process ID : {}".format(res.pid))
print("Return Code : {}".format(res.returncode))
print("Return Code After Waiting : {}".format(ret_code))
print("\nResult Output Stream : ")
for line in res.stdout:
print(line, end="")
print("\nResult Error Stream : ")
for line in res.stderr:
print(line, end="")
Our code for this part is exactly the same as our previous example with 2 minor changes. The first change is that we have introduced a timeout of 2 seconds inside of the wait() method call. Our second change is that we have wrapped the code inside of the try-except block to capture errors. We can notice from the output that the child process terminates with a time-out error.
try:
res = subprocess.Popen(["python", "addition.py"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf")
print("Returned Object Type : {}".format(res))
ret_code = res.poll()
print("Return Code Initially : {}".format(ret_code))
res.wait(timeout=2)
print("Process ID : {}".format(res.pid))
print("Return Code : {}".format(res.returncode))
print("\nResult Output Stream : ")
for line in res.stdout:
print(line, end="")
print("\nResult Error Stream : ")
for line in res.stderr:
print(line, end="")
except Exception as e:
print("ErrorType : {}, Error : {}".format(type(e).__name__, e))
As a part of our twelfth example, we'll explain how we can communicate with the child process (send inputs and receive results) using communicate() method of the Popen instance.
Our code for this part simply creates a Popen instance to run addition.py python script. We are then calling communicate() method to collect the standard output and standard error data. We are then printing data collected by the communicate() method. In this example, we have collected standard output and error data from communicate() method whereas, in previous examples, we had collected them from Popen instance.
res = subprocess.Popen(["python", "addition.py"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
text=True, encoding="utf")
stdout, stderr = res.communicate()
print("Returned Object Type : {}".format(res))
print("Process ID : {}".format(res.pid))
print("\nResult Output Stream : ")
print(stdout)
print("\nResult Error Stream : ")
print(stderr)
We are using our thirteenth example to demonstrate how we can pass data to the child process using communicate() method.
Our code for this part creates an instance of Popen which would run addition_user_input.py Python script as a child process. As part of this call, We have set stdin as well part from stdout and stderr. We have then called communicate() method giving it an input string that will be passed to the script. We have passed text parameter as True to indicate that input should be in string format else input is expected as bytes.
We can notice from the output that it seems to have correctly captured input data.
import subprocess
res = subprocess.Popen(["python", "addition_user_input.py"],
stdin=subprocess.PIPE,stdout=subprocess.PIPE, stderr=subprocess.PIPE,
text=True, encoding="utf")
stdout, stderr = res.communicate(input="20\n25\n20\n25\n")
print("Returned Object Type : {}".format(res))
print("Process ID : {}".format(res.pid))
print("\nResult Output Stream : ")
print(stdout)
print("\nResult Error Stream : ")
print(stderr if stderr else "No Error")
Our code for this example is exactly the same as our previous example with few minor changes. We have not set text parameter of the Popen() constructor hence input data is expected as bytes. We have then given input data through communicate() method in bytes format.
When we run this part of the code, it has the same output as the previous example even though input data was given in binary format.
res = subprocess.Popen(["python", "addition_user_input.py"],
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = res.communicate(input=bytes("20\n20\n20\n20\n", encoding="utf"))
print("Returned Object Type : {}".format(res))
print("Process ID : {}".format(res.pid))
print("\nResult Output Stream : ")
print(stdout.decode())
print("\nResult Error Stream : ")
print(stderr.decode() if stderr else "No Error")
As a part of our fourteenth example, we'll explain how we can kill the child process in three different ways by using three different methods of the Popen instance.
Our code for this example is exactly the same as the 11th example with the addition of few lines of code. We are again running addition.py script which takes 6 seconds to complete. We are asking the main process to sleep for 4 seconds and after that, we are checking whether the child process has completed or not using poll() method. If it's still not completed after 4 seconds then, we kill it using kill() method.
We can notice from the output that after we have killed the process there is no standard output and error data.
import time
res = subprocess.Popen(["python", "addition.py"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf")
print("Returned Object Type : {}".format(res))
ret_code = res.poll()
print("Return Code Initially : {}".format(ret_code))
time.sleep(4) ## Ask main process to sleep for 4 seconds.
if not res.poll():
print("Process not completed after waiting for 4 seconds. Killing it")
res.kill()
print("Process ID : {}".format(res.pid))
print("Return Code : {}".format(res.returncode))
print("Return Code After Waiting : {}".format(ret_code))
print("\nResult Output Stream : ")
for line in res.stdout:
print(line, end="")
print("\nResult Error Stream : ")
for line in res.stderr:
print(line, end="")
Our code for this example is exactly the same as the previous code but with the only difference that we are using, terminate() method to kill the process.
import time
res = subprocess.Popen(["python", "addition.py"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf")
print("Returned Object Type : {}".format(res))
ret_code = res.poll()
print("Return Code Initially : {}".format(ret_code))
time.sleep(4)
if not res.poll():
print("Process not completed after waiting for 4 seconds. Killing it")
res.terminate()
print("Process ID : {}".format(res.pid))
print("Return Code : {}".format(res.returncode))
print("Return Code After Waiting : {}".format(ret_code))
print("\nResult Output Stream : ")
for line in res.stdout:
print(line, end="")
print("\nResult Error Stream : ")
for line in res.stderr:
print(line, end="")
Our code for this example is exactly the same as our previous two examples with the only change that we are killing the process by using send_signal() method. We are sending a method signal SIGTERM which will kill it.
import time
import signal
res = subprocess.Popen(["python", "addition.py"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf")
print("Returned Object Type : {}".format(res))
ret_code = res.poll()
print("Return Code Initially : {}".format(ret_code))
time.sleep(4)
if not res.poll():
print("Process not completed after waiting for 4 seconds. Killing it")
res.send_signal(signal.SIGTERM)
print("Process ID : {}".format(res.pid))
print("Return Code : {}".format(res.returncode))
print("Return Code After Waiting : {}".format(ret_code))
print("\nResult Output Stream : ")
for line in res.stdout:
print(line, end="")
print("\nResult Error Stream : ")
for line in res.stderr:
print(line, end="")
This ends our small tutorial explaining the API of subprocess module which can be used to launch a program installed on the system as a child process from Python. Please feel free to let us know your views in the comments section.
If you are more comfortable learning through video tutorials then we would recommend that you subscribe to our YouTube channel.
When going through coding examples, it's quite common to have doubts and errors.
If you have doubts about some code examples or are stuck somewhere when trying our code, send us an email at coderzcolumn07@gmail.com. We'll help you or point you in the direction where you can find a solution to your problem.
You can even send us a mail if you are trying something new and need guidance regarding coding. We'll try to respond as soon as possible.
If you want to