在Ubuntu 18.04上Python的os.system和subprocess.check_output中无法解释的shell命令转义行为

时间:2018-11-02 19:00:12

标签: python ubuntu subprocess ubuntu-18.04 os.system

我对在Ubuntu 18.04上传递给blank=True的命令中Python不能反斜杠转义感到困惑(在CentOS上一切正常)。考虑这个程序:

os.system

当我在Ubuntu 18.04上运行它时,我得到了:

#!/usr/bin/env python
import os
import sys
import subprocess

def get_command(n):
    return "echo 'Should be %d backslashes: %s'" % (n, "\\" * n)

print("")
print("Using os.system directly:")
print("")
for n in range(1, 5):
    os.system(get_command(n))

print("")
print("Using subprocess.check_output:")
print("")
for n in range(1, 5):
    sys.stdout.write(subprocess.check_output(get_command(n), shell=True).decode('utf-8'))

print("")
print("Writing the bash code to a script and using os.system on the script:")
print("")
for n in range(1, 5):
    with open('/tmp/script.sh', 'w') as f:
        f.write(get_command(n))
    os.system('/bin/bash /tmp/script.sh')

请注意,它输出一个反斜杠,应该输出两个反斜杠,并输出两个反斜杠,它应该输出三个或四个!

但是,在我的CentOS 7机器上,一切正常。在两台计算机上,外壳均为Using os.system directly: Should be 1 backslashes: \ Should be 2 backslashes: \ Should be 3 backslashes: \\ Should be 4 backslashes: \\ Using subprocess.check_output: Should be 1 backslashes: \ Should be 2 backslashes: \ Should be 3 backslashes: \\ Should be 4 backslashes: \\ Writing the bash code to a script and using os.system on the script: Should be 1 backslashes: \ Should be 2 backslashes: \\ Should be 3 backslashes: \\\ Should be 4 backslashes: \\\\ 。这是脚本的python2.7调用的strace输出,以防万一:https://gist.githubusercontent.com/mbautin/a97cfb6f880860f5fe6ce1474b248cfd/raw

我猜想从Python调用shell命令最安全的行为就是将它们写入临时脚本文件中!

1 个答案:

答案 0 :(得分:3)

虽然我可以同意这种行为很奇怪,但这不是无法解释的。出现这种情况的原因与Python或subprocess无关。在C程序中,使用system对OS(Linux)的调用与在Python程序中完全一样。

虽然原因与您的shell有关,但与bash并不完全相同。原因是,使用os.system()来呼叫subprocess.Popen()subprocess.check_output()家族(包括shell=True)时。 documentation指出“在带shell = True的POSIX上,shell默认为/ bin / sh。” 因此,调用echo命令的shell不是{{ 1}},即使这是您的默认外壳程序以及您从中运行脚本/启动Python的外壳程序。

相反,您的命令由系统的bash执行。很长一段时间以来,这几乎指向所有Linux版本中的/bin/sh(以POSIX兼容模式运行),但是,最近在某些发行版中已经改变了,其中包括Ubuntu(显然不是CentOS,因为您没有这样做)。在那里看到相同的行为),现在/bin/bash指向/bin/sh了:

bin/dash

因此,您的脚本实际上是由$ ll /bin/sh lrwxrwxrwx 1 root root 4 sep 23 12:53 /bin/sh -> dash* 而不是dash执行的。 “为了提高效率”(请参见提示符bashman dash已选择在内部实现dash,而不是使用echo(由/bin/echo使用)。不幸的是,bash dash不如echo强大,并且对字符串输入有不同的解释,即/bin/echo dash会转义许多反斜杠命令,实际上意味着它“吞咽” 还有一个反斜杠。

通过指定echo选项(请参阅/bin/echo)可以使-e以相同的方式运行,但不幸的是,不可能有man echo内置dash转义反斜杠。

现在,这就是您看到的原因。避免该问题的一个好方法是依赖系统外壳程序调用。如果它是单个命令,例如echo,则最好根本不调用Shell,而删除echo标志。或者,如果您需要某些特定于Shell的功能,请自己控制外壳的调用。并且,在这种特殊情况下,第三种方法是在执行时显式指向shell=True,因为这样可以确保使用“标准” /bin/echo

echo

请注意,在不使用#!/usr/bin/env python3 import sys import subprocess import shlex def get_command(n): return "echo 'Should be {} backslahes: {}'".format(n, "\\"*n) print("") print("Using subprocess.check_output:") print("") for n in range(1, 5): # Direct invocation: cmd = get_command(n) sys.stdout.write(subprocess.check_output(shlex.split(cmd)).decode()) # Controlling invocation shell: bash_cmd = ['/bin/bash', '-c'] + [cmd] sys.stdout.write(subprocess.check_output(bash_cmd).decode()) # Using shell=True but point to /bin/echo echo_cmd = '/bin/' + cmd sys.stdout.write(subprocess.check_output(echo_cmd, shell=True).decode()) 的情况下,该命令应为shell=True而不是字符串。如图所示,可以为shlex.split()

在这些方法中,如果有可能某些参数来自不受信任的来源,则首选第一种方法(直接调用list,这是由于security concerns)。但是,在那种情况下,echo也不应使用,因为它会带来相同的安全漏洞。