os.popen uses the shell by default, and unlike subprocess.Popen, has no way of disabling it. Problems can occur when the program you are trying to run does not exist or is unable to be ran due to a permissions issue.
Consider the following example function:
def logged_in_users(): users = set() for line in os.popen("who"): users.add(line.split()) return users
This runs just fine when everything is working:
In : logged_in_users() Out: set(['justin'])
But if there is a problem running the command(for the example lets change the ‘who’ to ‘whom’:
In : logged_in_users() sh: whom: not found Out: set()
What happened was os.popen ran
"sh -c whom"
While sh started fine, the actually command could not be ran. Since os.popen also does not pass the exit code back to the parent process there is no easy method to use to see if anything went wrong.
If we switch over to subprocess.Popen, everything works fine:
for line in subprocess.Popen(["whom"], stdout=subprocess.PIPE).stdout:
This call will instead immediately raise an exception:
OSError: [Errno 2] No such file or directory
So using subprocess.Popen and not using os.popen has the following benefits:
- Is more secure against potential command injection
- Does not waste a process
- Returns better error message to the parent process