如何逃避 os.system()调用?

在使用 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 ()提供的一种快速而简单的解决方案,而且字符串的源不是用户生成的,或者至少是由受信任的用户(我)输入的。

110363 次浏览

我相信 os.system 只是调用为用户配置的任何命令 shell,所以我认为不能以独立于平台的方式进行。我的命令 shell 可以是 bash、 emacs、 ruby 甚至 quake3中的任何命令。这些程序中的一些程序并不期望您传递给它们的参数类型,即使它们这样做了,也不能保证它们以同样的方式进行转义。

我用的是这个:

def shellquote(s):
return "'" + s.replace("'", "'\\''") + "'"

Shell 将始终接受一个带引号的文件名,并在将其传递给有问题的程序之前删除周围的引号。值得注意的是,这避免了包含空格或任何其他类型的恶心 shell 元字符的文件名的问题。

更新 : 如果您正在使用 Python 3.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]

从 Python 3开始,shlex.quote() 做你想做的事情。

(使用 pipes.quote同时支持 python2和 python3, 不过请注意,自3.10以来,pipes已被弃用 3.13)内载的资料及建议

注意,pipees.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

请参阅 巨蟒第7476期; 它已经在 Python 2.6和3.2以及更新版本中得到了修复。

我使用的函数是:

def quote_argument(argument):
return '"%s"' % (
argument
.replace('\\', '\\\\')
.replace('"', '\\"')
.replace('$', '\\$')
.replace('`', '\\`')
)

也就是说: 我总是将参数放在双引号中,然后反斜杠-引号放在双引号中的唯一特殊字符中。

也许 subprocess.list2cmdline是一个更好的拍摄?

注意 : 这是 Python 2.7.x 的答案。

根据 来源pipes.quote()是一种“ 可靠地引用字符串作为 < strong >/bin/sh 的单个参数 ”的方法。(尽管它是 从2.7版本开始就不再使用了,并且最终在 Python 3.3中作为 shlex.quote()函数公开发布。)

另一只手上,subprocess.list2cmdline()是“ 使用与 < strong > MS C 运行时相同的规则,将一系列参数转换为命令行字符串 ”的一种方式。

这就是为命令行引用字符串的独立于平台的方法。

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)

在诸如 Bash 这样的 UNIX shell 上,您可以在 Python 3中使用 shlex.quote来转义 shell 可能解释的特殊字符,比如空格和 *字符:

import os
import shlex


os.system("rm " + shlex.quote(filename))

但是,这对于安全目的来说是不够的!您仍然需要注意,命令参数不会以意想不到的方式进行解释。例如,如果文件名实际上是一个类似于 ../../etc/passwd的路径,该怎么办?运行 os.system("rm " + shlex.quote(filename))可能会删除 /etc/passwd,而你只希望它删除在工作目录中找到的文件名!这里的问题不是 shell 解释特殊字符的问题,而是文件名参数没有被 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))

我认为这些答案对于逃避 Windows 上的命令行参数来说是一个坏主意。根据调查结果: 人们试图使用黑名单方法来过滤“坏”字符,假设(并希望)他们得到了所有这些字符。Windows 非常复杂,未来可能会出现各种各样的字符,这些字符可能允许攻击者劫持命令行参数。

我已经看到一些答案忽略了过滤 Windows 中的基本元字符(如分号)我采取的方法要简单得多:

  1. 列出允许使用的 ASCII 字符。
  2. 删除列表中不包含的所有字符。
  3. 转义斜杠和双引号。
  4. 用双引号包围整个命令,这样命令参数就不会被恶意破坏或用空格命令。

一个基本的例子:


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 变量的选项。启用此选项会带来更大的风险,因此默认情况下将其禁用。