【问题标题】:Why does shell=True eat up the arguments, and what is the fix?为什么 shell=True 会吃掉参数,解决方法是什么?
【发布时间】:2018-01-17 23:45:27
【问题描述】:

我使用的是 subprocess.Popen 而不是 os.fork(),并尝试使用“shell=True”构造。但是,要传递给子进程的参数将被删除。知道为什么,有什么可以解决的吗?

附加的文件是“/tmp/a.py”和“/tmp/a.pl”。如果我在没有任何参数的情况下运行 a.py,我会得到预期的结果。带参数,错误消息。

#!/opt/local/bin/python3.6                                                                                                                     

import sys, subprocess

class child_proc:
    def __init__ (self, useShell):
        print ("useShell:", useShell)
        acmd = ["/tmp/a.pl", "Hello", "World"]
        if useShell:
            proc = subprocess.Popen(acmd, stdout=subprocess.PIPE, shell=True)
        else:
            proc = subprocess.Popen(acmd, stdout=subprocess.PIPE)
        while True:
            line = proc.stdout.readline()
            if line:
                try:
                    aline = line.decode().rstrip()
                    print (aline)
                except UnicodeDecodeError as e:
                    print("Error decoding child output:", line, e)
                    break
            else:
                break

child_proc(len(sys.argv) > 1)

它正在调用的脚本 -

#!/opt/local/bin/perl -w                                                                                                                       
die "[a.pl] Missing arguments\n" if $#ARGV < 0;
print "[a.pl] @ARGV\n";
exit 0;

这是在 MacOS10.13.1 上。感谢您的洞察力。

【问题讨论】:

  • 来自文档:“如果 shell 为 True,建议将 args 作为字符串而不是序列传递。”另外,你不应该使用Popen,而是runcheckoutput,你也不应该使用shell=True,你也应该使用shell=useShell而不是if/else。当您实际上不使用argv 时,为什么首先要这样做?
  • 我在这里得到的是,如果我使用 Shell=True,我需要使用 ' '.join(acmd) 而不是 acmd - 这解决了问题。

标签: python


【解决方案1】:

来自the docs

在带有shell=True 的Unix 上,shell 默认为/bin/sh。如果 args 是 string,字符串指定要通过shell执行的命令。 ... 如果 args 是一个序列,第一项指定命令字符串,任何 附加项将被视为 shell 本身的附加参数。

所以如果shell=True,你应该传递一个字符串而不是一个参数列表,这将由shell逐字解释。如果您像现在一样传递一个序列,它会将第一个元素视为整个命令,而将其余元素视为 shell 的参数。所以如果你需要shell=True,传递一个字符串。

proc = subprocess.Popen("./tmp/a.pl Hello World", stdout=subprocess.PIPE, shell=True)

当你这样做时,小心不要将任何用户输入传递给 shell。正如文档警告的那样,带有 shell=True 参数的 Popen 的不受信任的输入是一个主要安全隐患。

【讨论】:

    【解决方案2】:

    当您使用list 作为命令容器时,使用shell=True,第一个元素将被视为要运行的命令,然后其余元素将作为参数传递给shell 本身。

    所以,如果你想使用shell=True来运行命令是一个shell,把它变成一个字符串:

    acmd = "/tmp/a.pl Hello World"
    

    注意:小心以非转义形式直接在 shell 中运行。

    或者,更好的是,您应该放弃shell=True 并让subprocess 执行fork()-exec(),因为您的代码中没有特定的shell。


    为了完整起见,如果您坚持保持当前结构,您应该这样做:

    if useShell:
        proc = subprocess.Popen("/tmp/a.pl Hello World", stdout=subprocess.PIPE, shell=True)
    else:
        proc = subprocess.Popen(["/tmp/a.pl", "Hello", "World"], stdout=subprocess.PIPE)
    

    或者您可以在开始时根据useShell 的值设置acmd,它会变得更简单,因为您也可以通过将shell 设置为useShellsubprocess.Popen 中删除if...else 构造:

    acmd = "/tmp/a.pl Hello World" if useShell else ["/tmp/a.pl", "Hello", "World"]
    proc = subprocess.Popen(acmd, stdout=subprocess.PIPE, shell=useShell)
    

    附带说明一下,您应该使用 snake_case 作为变量/函数名,使用 CamelCase 作为类名。

    【讨论】:

    • 为什么不首先shell=useShell
    • @hop:输入命令容器在两种情况下不同。我没有看到收益。
    • 我认为操作员无论如何都想构建acmd,所以会有“收益”
    • @hop 立即查看。
    • 我的原始脚本没有 Shell=True 部分。我无法杀死子进程(来自父进程中的另一个线程),并且阅读该站点指向使用 Shell=True。所以我在尝试这个,孩子打了个嗝。我将代码简化为 30 行以在这里提出问题。在我的现实世界中,使用 Shell 构造不依赖于任何参数,但 acmd 使用不同的逻辑构建为列表。
    猜你喜欢
    • 2012-05-26
    • 1970-01-01
    • 2011-05-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-16
    • 2012-02-01
    相关资源
    最近更新 更多