如何逃避 os.system() 调用?
- 2025-01-14 08:50:00
- admin 原创
- 124
问题描述:
使用 os.system() 时,通常需要转义文件名和作为参数传递给命令的其他参数。我该怎么做?最好是可以在多个操作系统/shell 上运行的东西,特别是对于 bash。
我目前正在执行以下操作,但确信必须有一个库函数,或者至少有一个更优雅/强大/高效的选项:
def sh_escape(s):
return s.replace("(","\\(").replace(")","\\)").replace(" ","\\ ")
os.system("cat %s | grep something | sort > %s"
% (sh_escape(in_filename),
sh_escape(out_filename)))
编辑:我接受了使用引号的简单答案,不知道为什么我没有想到这一点;我猜是因为我来自 Windows,其中 ' 和 " 的行为略有不同。
关于安全性,我理解这种担忧,但在这种情况下,我对 os.system() 提供的快速简便的解决方案感兴趣,并且字符串的来源不是用户生成的,或者至少是由受信任的用户(我)输入的。
解决方案 1:
shlex.quote()
从 python 3 开始可以做你想做的事情。
(pipes.quote
用于支持 Python 2 和 Python 3,但请注意,pipes
自 3.10 以来已被弃用,并计划在 3.13 中删除)
解决方案 2:
这是我使用的:
def shellquote(s):
return "'" + s.replace("'", "'\\''") + "'"
shell 始终会接受带引号的文件名,并在将其传递给相关程序之前删除周围的引号。值得注意的是,这可以避免文件名中包含空格或任何其他令人讨厌的 shell 元字符的问题。
更新:如果您使用的是 Python 3.3 或更高版本,请使用shlex.quote而不是自行使用。
解决方案 3:
也许您有使用 的特定原因os.system()
。但如果没有,您可能应该使用subprocess
模块。您可以直接指定管道并避免使用 shell。
以下内容来自PEP324:
Replacing shell pipe line ------------------------- output=`dmesg | grep hda` ==> p1 = Popen(["dmesg"], stdout=PIPE) p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE) output = p2.communicate()[0]
解决方案 4:
也许subprocess.list2cmdline
是更好的机会?
解决方案 5:
请注意,pipes.quote 实际上在 Python 2.5 和 Python 3.1 中已损坏且使用不安全——它不处理零长度参数。
>>> from pipes import quote
>>> args = ['arg1', '', 'arg3']
>>> print 'mycommand %s' % (' '.join(quote(arg) for arg in args))
mycommand arg1 arg3
请参阅Python 问题 7476;它已在 Python 2.6 和 3.2 及更新版本中修复。
解决方案 6:
我相信 os.system 只是调用为用户配置的任何命令 shell,所以我认为你不能以独立于平台的方式做到这一点。我的命令 shell 可以是 bash、emacs、ruby 甚至 quake3 中的任何一个。其中一些程序并不期望你传递给它们的参数类型,即使它们期望,也不能保证它们以相同的方式进行转义。
解决方案 7:
注意:这是针对 Python 2.7.x 的答案。
根据来源,是一种“可靠地将字符串引用为/bin/shpipes.quote()
的单个参数”的方法。(尽管它从 2.7 版开始就被弃用了,并最终在 Python 3.3 中作为函数公开。)shlex.quote()
另一方面,是一种“将一系列参数转换为命令行字符串,使用与MS C 运行时subprocess.list2cmdline()
相同的规则”的方法。
这就是我们为命令行引用字符串的平台无关的方式。
import sys
mswindows = (sys.platform == "win32")
if mswindows:
from subprocess import list2cmdline
quote_args = list2cmdline
else:
# POSIX
from pipes import quote
def quote_args(seq):
return ' '.join(quote(arg) for arg in seq)
用法:
# Quote a single argument
print quote_args(['my argument'])
# Quote multiple arguments
my_args = ['This', 'is', 'my arguments']
print quote_args(my_args)
解决方案 8:
我使用的功能是:
def quote_argument(argument):
return '"%s"' % (
argument
.replace('\\', '\\\\')
.replace('"', '\\\"')
.replace('$', '\\$')
.replace('`', '\\`')
)
也就是说:我总是将参数括在双引号中,然后用反斜杠引用双引号内的特殊字符。
解决方案 9:
在 Bash 等 UNIX shell 上,您可以shlex.quote
在 Python 3 中使用来转义 shell 可能解释的特殊字符,例如空格和*
字符:
import os
import shlex
os.system("rm " + shlex.quote(filename))
但是,这对于安全目的来说还不够!您仍然需要小心,不要以非预期的方式解释命令参数。例如,如果文件名实际上是像这样的路径,该怎么办../../etc/passwd
?当您只希望它删除在当前目录中找到的文件名时,运行os.system("rm " + shlex.quote(filename))
可能会删除/etc/passwd
!这里的问题不是 shell 解释特殊字符,而是 filename 参数没有被解释为rm
简单的文件名,它实际上被解释为路径。
或者,如果有效文件名以破折号开头,例如 ,会怎么样-f
?仅仅传递转义文件名是不够的,您需要使用 禁用选项--
,或者您需要传递不以破折号开头的路径,例如./-f
。这里的问题不是 shell 解释特殊字符,而是如果参数以破折号开头,rm
命令会将参数解释为文件名、路径或选项。
这是一个更安全的实现:
if os.sep in filename:
raise Exception("Did not expect to find file path separator in file name")
os.system("rm -- " + shlex.quote(filename))
解决方案 10:
我认为这些答案对于在 Windows 上转义命令行参数来说不是一个好主意。根据结果:人们试图应用黑名单方法来过滤“坏”字符,假设(并希望)他们能过滤掉所有字符。Windows 非常复杂,将来可能会发现各种各样的字符,这些字符可能允许攻击者劫持命令行参数。
我已经看到一些答案忽略了过滤 Windows 中的基本元字符(例如分号)。我采取的方法要简单得多:
列出允许的 ASCII 字符。
删除所有不在该列表中的字符。
转义斜杠和双引号。
用双引号将整个命令括起来,这样命令参数就不会被恶意破坏和被空格占用。
一个基本的例子:
def win_arg_escape(arg, allow_vars=0):
allowed_list = """'"/\\abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-. """
if allow_vars:
allowed_list += "~%$"
# Filter out anything that isn't a
# standard character.
buf = ""
for ch in arg:
if ch in allowed_list:
buf += ch
# Escape all slashes.
buf = buf.replace("\\\", "\\\\\")
# Escape double quotes.
buf = buf.replace('"', '""')
# Surround entire arg with quotes.
# This avoids spaces breaking a command.
buf = '"%s"' % (buf)
return buf
该函数有一个选项可以启用环境变量和其他 shell 变量。启用该选项会带来更多风险,因此默认情况下禁用该选项。