如何避免使用 Popen 将 shell 构造传递给可执行文件

How to avoid passing shell constructs to executable using Popen

我正在尝试调用名为 foo 的可执行文件,并向其传递一些命令行参数。外部脚本调用可执行文件并使用以下命令:

./main/foo --config config_file 2>&1 | /usr/bin/tee temp.log

脚本使用Popen执行此命令如下:

from subprocess import Popen
from subprocess import PIPE
def run_command(command, returnObject=False):
    cmd = command.split(' ')
    print('%s' % cmd)
    p = None
    print('command : %s' % command)
    if returnObject:
        p = Popen(cmd)
    else:
        p = Popen(cmd)
        p.communicate()
        print('returncode: %s' % p.returncode)
        return p.returncode
    return p

command = "./main/foo --config config_file 2>&1 | /usr/bin/tee temp.log
"
run_command(command)

但是,这会将额外的参数 ['2>&1'、'|'、'/usr/bin/tee'、'temp.log'] 传递给 foo 可执行文件。

如何在保持功能的同时摆脱这些传递给 foo 的额外参数? 我已经尝试 shell=True 但阅读了有关出于安全目的避免它的信息(shell 注入攻击)。寻找一个简洁的解决方案。

谢谢

更新: - 更新了 tee 命令后的文件

您可以合并两个 Whosebug 问题的答案:
1. piping together several subprocesses
x | y 问题
2. Merging a Python script's subprocess' stdout and stderr (while keeping them distinguishable)
2>&1 问题

字符串

./main/foo --config config_file 2>&1 | /usr/bin/tee >temp.log

...充满了 shell 结构。 如果游戏中没有 shell,这些就没有任何意义。因此,您有两个选择:

  • 设置shell=True
  • 将它们替换为本机 Python 代码。

例如,2>&1 与将 stderr=subprocess.STDOUT 传递给 Popen 是一样的,而您的 T 恤——因为它的输出被重定向并且没有传递任何参数——可能只是替换为 stdout=open('temp.log', 'w').


因此:

p = subprocess.Popen(['./main/foo', '--config', 'config_file'],
  stderr=subprocess.STDOUT,
  stdout=open('temp.log', 'w'))

...或者,如果您确实确实想要tee命令,但只是使用不当(也就是说,如果您想要tee temp.log, 而不是 tee >temp.log):

p1 = subprocess.Popen(['./main/foo', '--config', 'config_file'],
  stderr=subprocess.STDOUT,
  stdout=subprocess.PIPE)
p2 = subprocess.Popen(['tee', 'temp.log'], stdin=p1.stdout)
p1.stdout.close() # drop our own handle so p2's stdin is the only handle on p1.stdout
stdout, _ = p2.communicate()

将其包装在一个函数中,并检查两端是否成功可能如下所示:

def run():
    p1 = subprocess.Popen(['./main/foo', '--config', 'config_file'],
      stderr=subprocess.STDOUT,
      stdout=subprocess.PIPE)
    p2 = subprocess.Popen(['tee', 'temp.log'], stdin=p1.stdout)
    p1.stdout.close() # drop our own handle so p2's stdin is the only handle on p1.stdout
    # True if both processes were successful, False otherwise
    return (p2.wait() == 0 && p1.wait() == 0)

顺便说一句——如果您想使用 shell=True 和 return foo 的退出状态,而不是 tee,事情会变得更有趣.考虑以下因素:

p = subprocess.Popen(['bash', '-c', 'set -o pipefail; ' + command_str])

...pipefail bash 扩展将强制 shell 以第一个管道组件失败的状态退出(如果没有组件失败则为 0),而不是而不是仅使用最终组件的退出状态。

除了 之外,还有几个 "neat" 代码示例。

到运行Python中的shell命令:

#!/usr/bin/env python
from subprocess import check_call

check_call("./main/foo --config config_file 2>&1 | /usr/bin/tee temp.log",
           shell=True)

没有 shell:

#!/usr/bin/env python
from subprocess import Popen, PIPE, STDOUT

tee = Popen(["/usr/bin/tee", "temp.log"], stdin=PIPE)
foo = Popen("./main/foo --config config_file".split(), 
            stdout=tee.stdin, stderr=STDOUT)
pipestatus = [foo.wait(), tee.wait()]

注意:不要将 "command arg".split() 与非文字字符串一起使用。

How do I use subprocess.Popen to connect multiple processes by pipes?