English 中文(简体)
如何转义os.system()调用?
原标题:
  • 时间:2008-08-30 09:27:24
  •  标签:

使用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()提供的快速简单的解决方案感兴趣,并且字符串的来源要么不是用户生成的,要么至少是由受信任的用户(我)输入的。

最佳回答

这是我使用的:

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

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

更新:如果您使用的是Python 3.3或更高版本,请使用shlex。quote而不是自己滚动。

问题回答

shlex.quote()从python 3开始就可以执行您想要的操作。

(Use pipes.quote to support both python 2 and python 3, though note that pipes has been deprecated since 3.10 and slated for removal in 3.13)

也许您使用os.system()有特定的原因。但如果没有,您可能应该使用子流程模块。您可以直接指定管道,避免使用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]

也许子流程.list2cmdline是更好的选择?

请注意,pipes.quote在Python2.5和Python3.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及更新版本中修复。

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

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

根据sourcepipes.quote()是一种“可靠地引用字符串作为/bin/sh的单个参数”的方法。(尽管它是自2.7版本以来已弃用,并最终在Python 3.3中公开为shlex.quote()函数。)

另一方面subprocess.list2cmdline()是一种“使用与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)

我使用的功能是:

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

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

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

import os
import shlex

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

然而,这还不够安全!您仍然需要小心,不要以意外的方式解释命令参数。例如,如果文件名实际上是一个类似于../..的路径,该怎么办/etc/passwd?运行os.system(“rm”+shlex.requote(文件名))可能会删除/etc/passwd,而您只希望它删除当前目录中的文件名!这里的问题不是shell解释特殊字符,而是rm没有将filename参数解释为简单的文件名,它实际上被解释为路径。

或者,如果有效的文件名以破折号开头,例如-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. Make a list of allowed ASCII characters.
  2. Remove all chars that aren t in that list.
  3. Escape slashes and double-quotes.
  4. Surround entire command with double quotes so the command argument cannot be maliciously broken and commandeered with spaces.

一个基本的例子:


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





相关问题