在 python 内打开 Mongod,如何避免 `shell=True`

Opening Mongod within python, how to avoid `shell=True`

我正在尝试编写一个 python 脚本来启动 mongod、创建数据库(或打开我已经创建的数据库)、添加一些信息,然后关闭 mongod。

#!/usr/bin/env python

from pymongo import MongoClient
import subprocess

def create_mongo_database(database_name, path_to_database):
    mongod = subprocess.Popen(
        "mongod --dbpath {0}".format(path_to_database),
        shell=True
    )
    client = MongoClient()
    db = client[database_name]
    collection = db['test_collection']
    collection.insert_one({'something new':'some data'})
    mongod.terminate()

此代码有效,但阅读 python 文档,他们说在子进程中使用 shell=True 是个坏主意。我对这些东西很新手,我真的不明白 shell=True 标志在做什么,但我知道在输入变量时访问 shell 是不好的。问题是,当我尝试 运行 删除 shell=True 参数时,出现以下错误:

Traceback (most recent call last):
  File "/Users/KBLaptop/computation/kvasir/mongo_test2.py", line 23, in <module>
    create_mongo_database('test5_database', '~/computation/db')
  File "/Users/KBLaptop/computation/kvasir/mongo_test2.py", line 12, in create_mongo_database
    "mongod --dbpath {0}".format(path_to_database),
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 709, in __init__
    errread, errwrite)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 1326, in _execute_child
    raise child_exception
OSError: [Errno 2] No such file or directory

不确定它是否重要,但在工作案例和失败案例中,我在 sublime text3 的脚本末尾 运行 将其与 create_mongo_database('test5_database', '~/computation/db') 结合使用。

所以我的问题是 - 在这种情况下使用 shell=True 是否危险?如果我不执行 shell=True,为什么程序不会 运行?

编辑:根据 Dano 和 Charles Duffy 的解释,我现在将命令更改为:

mongod = subprocess.Popen(
    ["mongod", "--dbpath", path_to_database],
)

但是,如果 path_to_database 包含 ~/,这仍然不起作用。换句话说,/Users/myusername/path/to/db 有效,但 ~/path/to/db 无效。我最初的问题得到了很好的回答,我绝对可以完成这项工作,不确定这个新问题是否应该成为一个新问题...

如果您不使用 shell=True,则需要将命令拆分为各个参数。最简单的方法是使用 shlex.split:

 mongod = subprocess.Popen(
        shlex.split("mongod --dbpath {0}".format(os.path.expanduser(path_to_database)))
    )

编辑: Charles Duffy 指出,在这种情况下使用 shlex.split 不会对所有可能的路径都正常运行。最好直接用 shell=False 显式传递一个数组。有关详细信息,请参阅他的回答。

shell=True 命令告诉 Python 使用底层命令提示符(例如 bash、sh 等)执行您的命令。 shell=True 被认为是危险的原因是,如果您将用户定义的字符串传递到命令中,他们可能会制作一个将执行任意代码的命令。因此,在您的示例中,如果 path_to_database 由用户提供,想象一下他们是否通过了此:"; ls /"。当您执行 shell 中的命令时,; 字符被视为命令分隔符,除了 mongod 命令之外,您最终会执行 ls /。显然,这很糟糕。

如果您改用 shell=False,那么 ; ls / 字符将仅被视为 mongod 命令的参数,而不是传递给 shell,其中 ; 具有特殊含义。

综上所述,如果 path_to_database 不是并且永远不会由用户提供,那么使用 shell=True 应该是安全的,但一般来说,最好只如果确实需要,请使用它。

我实际上非常不同意现有的答案(建议 shlex.split())。如果您传入的 shell 引号字符串可能包含未知数量的参数,那么这是有道理的——但在这种情况下,您 确切地 知道您想要多少个参数: 你想要三个,不多也不少,你想确定 path_to_database 只是一个参数。

因此,适当的使用(如果想要波浪线扩展行为)是:

mongod = subprocess.Popen(['mongod', '--dbpath', os.path.expanduser(path_to_database)])

否则,包含空格的路径将被拆分为多个参数,包含文字引号(它们在 UNIX 上是合法的)的路径会将这些引号视为 escaping/syntax 而不是数据。使用 shell=True 可以做这两个事情,甚至更多——使 shlex.split() 和默认的 shell=False 肯定更安全——但传递一个显式数组更好。