I have a Python script that needs to call openssl.exe in Windows. Here’s the script:
import os import subprocess #print("Starting startfile method") #os.startfile('openssl.exe req -out server.csr -newkey:4096 -nodes -keyout server.priv.key -config server.cnf') #print("Starting Popen method") #subprocess.Popen('openssl.exe req -out server.csr -newkey rsa:4096 -nodes -keyout server.priv.key -config server.cnf') #print("Starting run method") #subprocess.run(['openssl.exe', 'req -out server.csr -newkey rsa:4096 -nodes -keyout server.priv.key -config server.cnf']) #print("Starting call method") #subprocess.call('openssl.exe req -out server.csr -newkey rsa:4096 -nodes -keyout server.priv.key -config server.cnf') #print("Done")
If I uncomment the “os.startfile” line, I get a file not found error. That makes sense. os.startfile can’t handle arguments (is that correct?)
If I uncomment the subprocess.Popen line, openssl starts, performs as it should, then hangs. openssl.exe process never exits, and so the script doesn’t finish. That really perplexes me.
If I uncomment the subprocess.run line, then openssl.exe gives me an error that “req -out… is not a valid command” Is that because openssl.exe doesn’t like arguments given through stdin?
If I uncomment the subprocess.call line, openssl.exe behaves exactly as it should (that is, as if it were run from Windows command line), and it exits and allows the Python script to continue.
Can you help me understand the differences between the these different methods of calling external processes?
Advertisement
Answer
Correcting each of these in turn:
import os import subprocess print("Starting startfile method") os.startfile('openssl.exe', arguments='req -out server.csr -newkey:4096 -nodes -keyout server.priv.key -config server.cnf') print("Starting Popen method with shell=True (bad)") subprocess.Popen('openssl.exe req -out server.csr -newkey rsa:4096 -nodes -keyout server.priv.key -config server.cnf', shell=True) print("Starting Popen method with shell=False (good)") subprocess.Popen(['openssl.exe', 'req', '-out', 'server.csr', '-newkey', 'rsa:4096', '-nodes', '-keyout', 'server.priv.key', '-config', 'server.cnf']) print("Starting run method") subprocess.run(['openssl.exe', 'req', '-out', 'server.csr', '-newkey', 'rsa:4096', '-nodes', '-keyout', 'server.priv.key', '-config', 'server.cnf']) print("Starting call method") subprocess.call(['openssl.exe', 'req', '-out', 'server.csr', '-newkey', 'rsa:4096', '-nodes', '-keyout', 'server.priv.key', '-config', 'server.cnf') print("Done")
Rules that apply across all of these:
- When there’s no shell word-splitting on spaces, you can’t put multiple arguments together into a single space-separated string; they need to each be separate.
- For all
subprocess
-family items, word-splitting is only offered whenshell=True
. However, adding a shell creates room for bugs and security exposure; it should only be done when you have no other choice. - Windows is unlike every other major operating system in that it makes programs responsible for parsing their own command lines (and thus lets each program parse its own command line in a different, incompatible way from how any other program works, should it choose to override the standard C library default behavior). Thus, only the Windows-specific
os.startfile
method allows arguments to be passed in a single string without having a shell or other software component responsible for separating them.