Hi Pythonistas!
In the previous post we have discussed how to run shell commands using OS module.The os.system() method works for basic tasks but lacks flexibility. If we need to:
✅ Capture command output
✅ Handle errors properly
✅ Run commands securely
Then Python’s subprocess module is the way to go!
Using subprocess.run() for Better Control
Let’s start by executing a simple command and capturing its output.
code
In [35]: import subprocess
In [36]: result = subprocess.run(['ls', '-la'], capture_output=True, text=True)
In [37]: result
Out[37]: CompletedProcess(args=['ls', '-la'], returncode=0, stdout='total 35876\ndrwxrwxr-x 7 afsal afsal 4096 Feb 6 04:11 .\ndrwxrwxr-x 4 afsal afsal 4096 Jan 14 10:20 ..\ndrwxrwxr-x 6 afsal afsal 4096 Feb 6 04:10 app\n-rw-rw-r-- 1 afsal afsal 951 Jan 20 03:58 constants.py\n-rw-rw-r-- 1 afsal afsal 1917 Jan 13 03:09 fyers_data_socket.py\n-rw-rw-r-- 1 afsal afsal 2158 Jan 13 03:09 fyers_order_socket.py\n-rw-rw-r-- 1 afsal afsal 5446 Jan 13 03:09 fyers_utils.py\n-rw-rw-r-- 1 afsal afsal 36647434 Jan 16 09:11 instruments.json\n-rw-rw-r-- 1 afsal afsal 3058 Feb 6 10:58 kite_utils.py\n-rw-rw-r-- 1 afsal afsal 108 Jan 13 03:09 long_running.py\n-rwxrwxr-x 1 afsal afsal 672 Jan 13 03:09 manage.py\ndrwxrwxr-x 2 afsal afsal 4096 Feb 6 10:58 __pycache__\n-rw-rw-r-- 1 afsal afsal 944 Jan 16 09:11 requirements.txt\ndrwxrwxr-x 4 afsal afsal 4096 Jan 20 03:58 static\n-rw-rw-r-- 1 afsal afsal 13473 Feb 6 09:20 stratergies.py\ndrwxrwxr-x 3 afsal afsal 4096 Jan 23 03:51 trading_platform\n-rw-rw-r-- 1 afsal afsal 536 Feb 5 12:31 utilities.py\ndrwxrwxr-x 6 afsal afsal 4096 Jan 17 03:33 venv\n', stderr='')
In [38]: result.returncode
Out[38]: 0
In [39]: result.stdout
Out[39]: 'total 35876\ndrwxrwxr-x 7 afsal afsal 4096 Feb 6 04:11 .\ndrwxrwxr-x 4 afsal afsal 4096 Jan 14 10:20 ..\ndrwxrwxr-x 6 afsal afsal 4096 Feb 6 04:10 app\n-rw-rw-r-- 1 afsal afsal 951 Jan 20 03:58 constants.py\n-rw-rw-r-- 1 afsal afsal 1917 Jan 13 03:09 fyers_data_socket.py\n-rw-rw-r-- 1 afsal afsal 2158 Jan 13 03:09 fyers_order_socket.py\n-rw-rw-r-- 1 afsal afsal 5446 Jan 13 03:09 fyers_utils.py\n-rw-rw-r-- 1 afsal afsal 36647434 Jan 16 09:11 instruments.json\n-rw-rw-r-- 1 afsal afsal 3058 Feb 6 10:58 kite_utils.py\n-rw-rw-r-- 1 afsal afsal 108 Jan 13 03:09 long_running.py\n-rwxrwxr-x 1 afsal afsal 672 Jan 13 03:09 manage.py\ndrwxrwxr-x 2 afsal afsal 4096 Feb 6 10:58 __pycache__\n-rw-rw-r-- 1 afsal afsal 944 Jan 16 09:11 requirements.txt\ndrwxrwxr-x 4 afsal afsal 4096 Jan 20 03:58 static\n-rw-rw-r-- 1 afsal afsal 13473 Feb 6 09:20 stratergies.py\ndrwxrwxr-x 3 afsal afsal 4096 Jan 23 03:51 trading_platform\n-rw-rw-r-- 1 afsal afsal 536 Feb 5 12:31 utilities.py\ndrwxrwxr-x 6 afsal afsal 4096 Jan 17 03:33 venv\n'
In [40]:
capture_output=True : Captures command output instead of printing it.
text=True : Ensures output is returned as a string instead of bytes
result.returncode == 0 means the process completed succesfully let us check a failure case
Handling Errors Gracefully
Let’s see what happens when the command fails:
code
In [40]: result = subprocess.run(['ls', '/abcd'], capture_output=True, text=True)
In [41]: result.returncode
Out[41]: 2
In [42]: result.stderr
Out[42]: "ls: cannot access '/abcd': No such file or directory\n"
In [43]:
Real-Time Command Execution with Popen
If we need to process output as it’s generated (e.g., for long-running commands like ping), we use subprocess.Popen().
code
In [49]: process = subprocess.Popen(['ping', '-c', '10','google.com'], stdout=subprocess.PIPE, text=True)
In [50]: for line in process.stdout:
...: print(line)
...:
PING google.com (172.217.166.110) 56(84) bytes of data.
64 bytes from maa05s09-in-f14.1e100.net (172.217.166.110): icmp_seq=1 ttl=118 time=13.4 ms
64 bytes from maa05s09-in-f14.1e100.net (172.217.166.110): icmp_seq=2 ttl=118 time=13.4 ms
64 bytes from maa05s09-in-f14.1e100.net (172.217.166.110): icmp_seq=3 ttl=118 time=13.2 ms
64 bytes from maa05s09-in-f14.1e100.net (172.217.166.110): icmp_seq=4 ttl=118 time=13.5 ms
64 bytes from maa05s09-in-f14.1e100.net (172.217.166.110): icmp_seq=5 ttl=118 time=13.0 ms
64 bytes from maa05s09-in-f14.1e100.net (172.217.166.110): icmp_seq=6 ttl=118 time=13.1 ms
64 bytes from maa05s09-in-f14.1e100.net (172.217.166.110): icmp_seq=7 ttl=118 time=13.2 ms
64 bytes from maa05s09-in-f14.1e100.net (172.217.166.110): icmp_seq=8 ttl=118 time=13.1 ms
64 bytes from maa05s09-in-f14.1e100.net (172.217.166.110): icmp_seq=9 ttl=118 time=13.4 ms
64 bytes from maa05s09-in-f14.1e100.net (172.217.166.110): icmp_seq=10 ttl=118 time=13.3 ms
--- google.com ping statistics ---
10 packets transmitted, 10 received, 0% packet loss, time 9015ms
rtt min/avg/max/mdev = 13.048/13.265/13.525/0.155 ms
Why use Popen?
- Useful for long-running commands.
- Reads output line by line without waiting for the command to finish.
Choosing Between os.system() and subprocess
Feature | os.system() |
subprocess.run() |
subprocess.Popen() |
---|---|---|---|
Captures Output | ❌ No | ✅ Yes | ✅ Yes |
Error Handling | ❌ Limited | ✅ Better | ✅ Better |
Security | ❌ Risky | ✅ Safer | ✅ Safer |
Real-time Output | ❌ No | ❌ No | ✅ Yes |
The subprocess module gives us more power and flexibility when running shell commands in Python. While os.system() is simple, subprocess is the recommended approach for most cases.