Ubuntu 18.04 上 Python 的 os.system 和 subprocess.check_output 中无法解释的 shell 命令反转义行为
Inexplicable shell command un-escaping behavior in Python's os.system and subprocess.check_output on Ubuntu 18.04
我对 Python 在 Ubuntu 18.04 上传递给 os.system
的命令中反斜杠的反斜杠感到困惑(在 CentOS 上一切正常)。考虑这个程序:
#!/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')
当我在 Ubuntu 18.04 运行 它时,我得到这个:
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: \\
注意它应该输出两个的地方输出一个反斜杠,应该输出三个或四个的地方输出两个反斜杠!
但是,在我的 CentOS 7 盒子上一切正常。在两台机器上 shell 都是 /bin/bash
。这是脚本 python2.7 调用的 strace 输出,以防万一:https://gist.githubusercontent.com/mbautin/a97cfb6f880860f5fe6ce1474b248cfd/raw
我想从 Python 调用 shell 命令最安全的行为是将它们写入临时脚本文件!
虽然我同意这种行为很奇怪,但并非无法解释。这种行为是有原因的,与 Python 或 subprocess
无关。在 C 程序中可以看到完全相同的行为,使用 system
调用 OS (Linux) 与您的 Python 程序一样。
原因与您的 shell 有关,但不完全与 bash
有关。原因是当用shell=True
调用os.system()
或subprocess.Popen()
家族(包括subprocess.check_output()
)时。 documentation 指出 "On POSIX with shell=True, the shell defaults to /bin/sh." 因此,调用您的 echo
命令的 shell 不是 bash
即使是您的默认 shell 和您 运行 所在的 shell 您的 script/starting Python.
相反,您的命令由系统的 /bin/sh
执行。很长一段时间以来,几乎所有 Linux 版本都指向 /bin/bash
(POSIX 兼容模式中的 运行),然而,最近这在某些发行版中发生了变化,其中 Ubuntu(但显然不是 CentOS,因为你在那里看不到相同的行为),现在 /bin/sh
指向 bin/dash
:
$ ll /bin/sh
lrwxrwxrwx 1 root root 4 sep 23 12:53 /bin/sh -> dash*
因此,您的脚本实际上是由 dash
而不是 bash
执行的。 "for efficiency"(请参阅提示中的 man dash
)dash
已选择在内部实现 echo
而不是使用 /bin/echo
(由 bash
使用)。不幸的是, dash
echo
不如 /bin/echo
强大,并且对字符串输入有不同的解释,即 dash
echo
是否转义了一些反斜杠命令,实际上意味着 "swallows"
给你一个额外的反斜杠。
可以通过指定 -e
选项(参见 man echo
)使 /bin/echo
以相同的方式运行,但不幸的是,[=25] 是不可能的=] 内置 echo
到 不 转义反斜杠。
现在,这就是您看到的原因。避免该问题的一个好方法是 不 依赖系统 shell 调用。如果它是单个命令,例如 echo
,最好根本不要调用 shell,删除 shell=True
标志。或者,如果您需要某些 shell 特定功能,请自行控制 shell 的调用。并且,在这种特殊情况下,第三种方法是在执行时明确指向 /bin/echo
,因为这确保使用 "standard" 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
时,命令应该是 list
而不是字符串。这可以是 shlex.split(),如图所示。
在这些方法中,如果某些参数有可能来自不受信任的来源,则首选第一种方法(直接 echo
调用),因为 security concerns。但是,在这种情况下,也不应使用 shlex.split()
,因为它会带来相同的安全漏洞。
我对 Python 在 Ubuntu 18.04 上传递给 os.system
的命令中反斜杠的反斜杠感到困惑(在 CentOS 上一切正常)。考虑这个程序:
#!/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')
当我在 Ubuntu 18.04 运行 它时,我得到这个:
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: \\
注意它应该输出两个的地方输出一个反斜杠,应该输出三个或四个的地方输出两个反斜杠!
但是,在我的 CentOS 7 盒子上一切正常。在两台机器上 shell 都是 /bin/bash
。这是脚本 python2.7 调用的 strace 输出,以防万一:https://gist.githubusercontent.com/mbautin/a97cfb6f880860f5fe6ce1474b248cfd/raw
我想从 Python 调用 shell 命令最安全的行为是将它们写入临时脚本文件!
虽然我同意这种行为很奇怪,但并非无法解释。这种行为是有原因的,与 Python 或 subprocess
无关。在 C 程序中可以看到完全相同的行为,使用 system
调用 OS (Linux) 与您的 Python 程序一样。
原因与您的 shell 有关,但不完全与 bash
有关。原因是当用shell=True
调用os.system()
或subprocess.Popen()
家族(包括subprocess.check_output()
)时。 documentation 指出 "On POSIX with shell=True, the shell defaults to /bin/sh." 因此,调用您的 echo
命令的 shell 不是 bash
即使是您的默认 shell 和您 运行 所在的 shell 您的 script/starting Python.
相反,您的命令由系统的 /bin/sh
执行。很长一段时间以来,几乎所有 Linux 版本都指向 /bin/bash
(POSIX 兼容模式中的 运行),然而,最近这在某些发行版中发生了变化,其中 Ubuntu(但显然不是 CentOS,因为你在那里看不到相同的行为),现在 /bin/sh
指向 bin/dash
:
$ ll /bin/sh
lrwxrwxrwx 1 root root 4 sep 23 12:53 /bin/sh -> dash*
因此,您的脚本实际上是由 dash
而不是 bash
执行的。 "for efficiency"(请参阅提示中的 man dash
)dash
已选择在内部实现 echo
而不是使用 /bin/echo
(由 bash
使用)。不幸的是, dash
echo
不如 /bin/echo
强大,并且对字符串输入有不同的解释,即 dash
echo
是否转义了一些反斜杠命令,实际上意味着 "swallows"
给你一个额外的反斜杠。
可以通过指定 -e
选项(参见 man echo
)使 /bin/echo
以相同的方式运行,但不幸的是,[=25] 是不可能的=] 内置 echo
到 不 转义反斜杠。
现在,这就是您看到的原因。避免该问题的一个好方法是 不 依赖系统 shell 调用。如果它是单个命令,例如 echo
,最好根本不要调用 shell,删除 shell=True
标志。或者,如果您需要某些 shell 特定功能,请自行控制 shell 的调用。并且,在这种特殊情况下,第三种方法是在执行时明确指向 /bin/echo
,因为这确保使用 "standard" 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
时,命令应该是 list
而不是字符串。这可以是 shlex.split(),如图所示。
在这些方法中,如果某些参数有可能来自不受信任的来源,则首选第一种方法(直接 echo
调用),因为 security concerns。但是,在这种情况下,也不应使用 shlex.split()
,因为它会带来相同的安全漏洞。