Passing Variables to Subprocess.Popen

2019-01-24 08:28发布

问题:

I have a script which calls another python script by subprocess.Popen module. But since I have arguments stored in variable(s)

servers[server]['address']
servers[server]['port']
servers[server]['pass']

I am unable to perform the command

p = subprocess.Popen(["python mytool.py -a ", servers[server]['address'], "-x", servers[server]['port'], "-p", servers[server]['pass'], "some additional command"], shell=True, stdout=subprocess.PIPE)

Workaround:

I got it to work by combing @wilberforce & @ciphor's answer but by a little modification

command = "python mytool.py -a %s -x %s -p %s some additional command" % (servers[server]['address'], servers[server]['port'], servers[server]['pass'])
p = subprocess.Popen(command , shell=True, stdout=subprocess.PIPE)

It ceases to work if I added several variables in double quotes, it could take at max "2" variables and break I added more.

Thanks to everyone who answered!

回答1:

Drop shell=True. The arguments to Popen() are treated differently on Unix if shell=True:

import sys
from subprocess import Popen, PIPE

# populate list of arguments
args = ["mytool.py"]
for opt, optname in zip("-a -x -p".split(), "address port pass".split()):
    args.extend([opt, str(servers[server][optname])])
args.extend("some additional command".split())

# run script
p = Popen([sys.executable or 'python'] + args, stdout=PIPE)
# use p.stdout here...
p.stdout.close()
p.wait()

Note that passing shell=True for commands with external input is a security hazard, as described by a warning in the docs.



回答2:

When you call subprocess.Popen you can pass either a string or a list for the command to be run. If you pass a list, the items should be split in a particular way.

In your case, you need to split it something like this:

command = ["python",  "mytool.py", "-a", servers[server]['address'], 
           "-x", servers[server]['port'], 
           "-p", servers[server]['pass'], 
           "some",  "additional", "command"]
p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)

This is because if you pass in a list, Popen assumes you have already split the command line into words (the values that would end up in sys.argv), so it doesn't need to.

The way you're calling it, it will try to run a binary called "python mytool.py -a", which isn't what you meant.

The other way to fix it is to join all of the words into a string (which Popen will then split up - see subprocess.list2cmdline). But you're better off using the list version if possible - it gives simpler control of how the commandline is split up (if arguments have spaces or quotes in them, for example) without having to mess around with quoting quote characters.



回答3:

You should concatenate the command to a whole string:

p = subprocess.Popen("python mytool.py -a " + servers[server]['address'] + " -x " + servers[server]['port'] + " -p " + servers[server]['pass'] + " some additional command", shell=True, stdout=subprocess.PIPE)


回答4:

Your problem in type str for first Popen argument. Replace it to list. Below code can work:

address = servers[server]['address']
port = servers[server]['port']
pass = servers[server]['pass']

command = "python mytool.py -a %s -x %d -p %s some additional command" % (address, port, pass)
p = subprocess.Popen(command.split(), shell=True, stdout=subprocess.PIPE)
#                    ^^^^^^^^^^^^^^^

Furthermore you can play with shell, because it system-depend parameter.