将变量传递给 Subprocess.Popen

Posted

技术标签:

【中文标题】将变量传递给 Subprocess.Popen【英文标题】:Passing Variables to Subprocess.Popen 【发布时间】:2013-12-07 01:18:27 【问题描述】:

我有一个脚本,它通过 subprocess.Popen 模块调用另一个 python 脚本。但是由于我将参数存储在变量中

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

我无法执行命令

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)

【问题讨论】:

使用变量构建作为命令的字符串,或将它们作为参数列表传递。 将 Python 称为 Python 的子进程本身就是一种反模式;更好的解决方案通常是从子进程重构脚本,以便您可以import 它并直接从父脚本调用它。 种情况下,您确实需要子进程(例如,如果脚本使用了您需要在父进程中以不同方式处理的信号),但通常情况下,您可能不应该这样做。 另外,正如subprocess 文档中已经指出的那样,如果您的用例已经由subprocess.run() 和朋友中的一个高级函数处理,您应该避免使用Popen .基本上,除非您需要并行处理或与正在运行的进程交互,否则不要使用Popen(如果这样做,请注意等待Popen 不适合您的子进程等)。 【参考方案1】:

删除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()

请注意,将shell=True 传递给带有外部输入的命令存在安全隐患,如警告in the docs 所述。

【讨论】:

【参考方案2】:

当您调用subprocess.Popen 时,您可以传递要运行的命令的字符串或列表。如果您传递一个列表,则应以特定方式拆分项目。

在你的情况下,你需要像这样拆分它:

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)

这是因为如果您传入一个列表,Popen 会假定您已经将命令行拆分为单词(这些值将在 sys.argv 中结束),因此不需要这样做。

按照你的调用方式,它会尝试运行一个名为“python mytool.py -a”的二进制文件,这不是你的意思。

解决它的另一种方法是将所有单词连接成一个字符串(然后Popen 将拆分 - 请参阅subprocess.list2cmdline)。但是如果可能的话,你最好使用列表版本 - 它可以更简单地控制命令行的拆分方式(例如,如果参数中有空格或引号),而不必乱用引号字符。

【讨论】:

我用来存储输出的变量,但在打印时它是空的(我猜它还没有被执行)。 我想你的意思是当你从 p.stdout 读取时,没有输出?那是因为该命令没有运行。 实际上,shell=True 可能在这里弄得一团糟——除非您使用 globbing(例如,展开文件列表),否则最好将其关闭。 没有shell=true,脚本拒绝执行,显示“sh: mytool.sh command not found” 输入 mytool.sh 的完整路径——在命令行中输入“mytool.sh”时给出的任何内容。【参考方案3】:

第一个 Popen 参数类型为 str 的问题。将其替换为list。下面的代码可以工作:

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(), stdout=subprocess.PIPE)
#        it is a list^^^^^^^^^^^^^^^  shell=False

如果command 参数来自可信来源,您可以构造command 并将其与shell=True 一起使用:

import pipes as p
command = "python mytool.py -a  -x  -p  some additional command".format(p.quote(address), p.quote(port), p.quote(pass))
p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)

注意 1:用 shell=True 构造的 command 可能不安全。使用 pipes.quote() 以减少注入的可能性。注意 2pipes.quote() 已弃用,因为 python2;对于python3 使用shlex 模块。

【讨论】:

遇到了和以前类似的问题,但我现在解决了。谢谢:) 如果您传递带有shell=True 的列表,第一个参数将被视为一个shell 脚本,随后的参数作为该脚本的参数(它的$0$1 等)。这个shlex.split() 操作的结果不是一个打算以这种方式解析的列表。在实践中,它将运行python,不带任何参数。 相比上半场有了很大的进步。回复:后半部分,更好地展示使用pipes.quote()(或在Python 3 中shlex.quote())来创建可以安全地替换为shell命令的内容,而不是仅仅放入不安全的代码有一堆警告。 @CharlesDuffy,已更新现在我无法查看上次更新。请检查一下,谢谢。【参考方案4】:

您应该将命令连接到一个完整的字符串:

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)

【讨论】:

我已将输出保存到变量中,但没有输出或正在执行的脚本的跟踪。可能是直接输出错误来检查一下吗? 也添加“stderr=subprocess.PIPE” 如果有人给你一个包含$(rm -rf ~)的地址列表,你最好希望没有人使用这种基于连接的方法。这是非常不安全的;即使您确实想使用shell=True,(唯一)安全的方法是从脚本代码带外传递变量:p = subprocess.Popen(['python mytool.py -a "$1" -x "$2" -p "$3" some additional command', '_', str(servers[server]['address']), str(servers[server]['port']), str(servers[server]['pass'])], shell=True, stdout=subprocess.PIPE)【参考方案5】:

用例

运行n个shell脚本或python脚本

创建一个 run.py 文件,负责将参数(整数)传递给 shell 文件。

假设您喜欢运行 n(4) 个接受 1 参数的 shell 脚本或 python 脚本。

使用以下代码创建文件run.python

以下代码说明

instanceQty = 要运行的 shell 脚本数量 os.getcwd() = 当前文件的路径 mockScript.sh = shell 脚本 我与 run.py 放在同一目录中

运行shell脚本

# this is python file with name run.py
import subprocess,os
instanceQty = 4
for i in range(0, instanceQty):
    print(os.getcwd())
    subprocess.Popen(f"os.getcwd()/mockScript.sh i",shell=True,executable='/bin/bash')

运行python脚本

import subprocess,os,sys
instanceQty = 4
for i in range(0, instanceQty):
    print(os.getcwd())
    subprocess.Popen([sys.executable,f"os.getcwd()/mockScript.py",str(i)])
    

运行这个文件

python 运行.py

MacOS 权限问题

sudo chmod ug+x mockScript.sh

sudo chmod ug+x run.py

所有代码均在 Python 3.8.1 和 MacOs 12.0.1 环境下测试。

【讨论】:

以上是关于将变量传递给 Subprocess.Popen的主要内容,如果未能解决你的问题,请参考以下文章

如何将值从javascript变量传递给剃刀变量?

将类型变量传递给函数

将多个变量传递给视图?

将变量传递给游标

如何将环境变量传递给传递给xmllint的命令?

使用 Ajax 方法将变量传递给 python