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


123

使用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
当心安全问题!例如,如果out_filename是foo.txt;rm -rf /恶意用户可以添加更多由Shell直接解释的命令。
Steve Gury

6
在没有os.system的情况下,这在子进程根本无法选择的情况下也很有用。例如生成shell脚本。

理想的sh_escape函数可以;通过简单地创建一个名为的文件来逃避和空格并消除安全问题foo.txt\;\ rm\ -rf\ /
汤姆(Tom)2010年

在几乎所有情况下,都应该使用子进程,而不是os.system。调用os.system只是要求进行注入攻击。
allyourcode '16

Answers:


84

这是我用的:

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

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

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


7
@pixelbeat:这就是为什么他关闭单引号,添加转义的文字单引号,然后再次重新打开单引号的原因。
2009年

4
尽管这几乎不是shellquote函数的职责,但有趣的是,如果在该函数的返回值之前出现未加引号的反斜杠,这仍然会失败。士气:确保在安全可靠的代码中使用此代码(例如,硬编码命令的一部分),请勿将其附加到其他未引用的用户输入中。
2009年

10
请注意,除非您绝对需要shell功能,否则可能应该使用Jamie的建议。
2009年

6
类似的东西现在可以作为shlex.quote正式获得。
Janus Troelsen

3
这个答案中提供的功能比shlex或更好地执行Shell引用pipes。这些Python模块错误地认为特殊字符是需要被引用的唯一的事情,这意味着Shell关键字(如timecasewhile不期望的行为时)将被解析。因此,我建议在此答案中使用单引号例程,因为它不会尝试变得“聪明”,因此不会出现那些愚蠢的情况。
user3035772 '16

157

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

(用于pipes.quote同时支持python 2和python 3)


也有commands.mkarg。它还增加了一个引号空间(引号之外),这可能是不希望的,这是有趣的,它们的实现彼此之间是多么不同,并且比Greg Hewgill的答案要复杂得多。
劳伦斯·贡萨尔维斯

3
由于某些原因,管道模块pipes.quote标准库文档中
日,一天

1
两者均未记录在案;command.mkarg在3.x中已弃用并删除,而pipe.quote仍然保留。
贝尼(Beni Cherniavsky),帕斯金(Paskin)

9
更正:正式记录shlex.quote()在3.3中,pipes.quote()出于兼容性考虑保留。[ bugs.python.org/issue9723]
Beni Cherniavsky-

7
管道在Windows上不起作用-添加由双引号插入的单引号。
Nux 2014年

58

也许您有使用的特定原因os.system()。但是,如果不是这样,您可能应该使用该subprocess模块。您可以直接指定管道,并避免使用外壳。

以下是来自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]

6
subprocess(尤其是with check_call等)通常要好得多,但是在某些情况下,仍然可以使用脱壳程序。我遇到的主要问题是必须调用ssh远程命令时。
克雷格·林格

@CraigRinger,是的,ssh远程处理将我带到了这里。:PI希望ssh在这里有帮助。
尔根A.艾哈德

@JürgenA.Erhard它没有--execvp-remote选项(或默认情况下以这种方式工作),这似乎很奇怪。通过外壳执行所有操作似乎很笨拙且冒险。OTOH,ssh充满奇怪的怪癖,通常是在狭义的“安全性”观点下进行的工作,导致人们提出了更为不安全的解决方法。
Craig Ringer

10

也许subprocess.list2cmdline是更好的选择?


看起来不错。有趣的是,没有文档...(至少在docs.python.org/library/subprocess.html中)
汤姆(Tom

4
它无法正常逃避\:subprocess.list2cmdline(["'",'',"\\",'"'])' "" \ \"
蒂诺(Tino)2012年

它不能逃脱外壳扩展符号
grep

subprocess.list2cmdline()仅适用于Windows吗?
JS。

@JS是,list2cmdline符合Windows cmd.exe语法(请参阅Python源代码中的函数docstring)。shlex.quote符合Unix bourne shell语法,但是通常不需要,因为Unix对直接传递参数提供了很好的支持。Windows几乎要求您传递带有所有参数的单个字符串(因此需要适当的转义)。
eestrada

7

请注意,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及更高版本中修复。


4
您正在使用哪个版本的Python?2.6版似乎产生了正确的输出:mycommand arg1''arg3(虽然两个都是单引号,但是Stack Overflow上的字体很难分辨!)
Brandon Rhodes 2010年

4

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

根据消息来源,这pipes.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)

3

我相信os.system只会调用为用户配置的任何命令外壳,因此我认为您不能以与平台无关的方式进行操作。我的命令外壳可以是bash,emacs,ruby甚至quake3中的任何东西。这些程序中的某些程序并不期望您传递给它们的参数的种类,即使它们这样做了,也无法保证它们以相同的方式进行转义。


2
期望一个基本的或完全兼容POSIX的外壳程序(至少在所有地方,但使用Windows,无论如何都知道您拥有什么“外壳程序”)并非没有道理。os.system不使用$ SHELL,至少在这里不使用。

2

我使用的功能是:

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

即:我总是将参数用双引号引起来,然后用反斜杠将双引号内的特殊字符引起来。


请注意,您应该使用'\\“','\\ $'和'\`',否则转义就不会发生
JanKanis 2014年

1
此外,在某些(奇怪的)语言环境中使用双引号存在一些问题pipes.quote@JohnWiseman指出的建议修复方法也已损坏。因此,格雷格·休吉尔(Greg Hewgill)的答案就是使用的答案。(这也是常规情况下炮弹内部使用的炮弹。)
mirabilos

-3

如果您确实使用了system命令,我将尝试将os.system()调用中的内容列入白名单。

clean_user_input re.sub("[^a-zA-Z]", "", user_input)
os.system("ls %s" % (clean_user_input))

子进程模块是一个更好的选择,我建议尽量避免使用os.system / subprocess之类的东西。


-3

真正的答案是:首先不要使用os.system()。请subprocess.call改用并提供未转义的参数。


6
该问题包含一个子流程失败的示例。如果可以使用子流程,则应该确保。但是,如果不能...子流程不是万能的解决方案。哦,你的回答不回答所有的问题。
尔根·艾哈德

@JürgenA.ErhardOP的示例不会因为他要使用外壳管道而失败吗?您应该始终使用子进程,因为它不使用外壳程序。这是一个笨拙的示例,但是您可以在本机子进程中进行管道处理,有一些pypi软件包试图使之更容易。我倾向于尽可能多地在python中进行所需的后处理。您始终可以制作自己的StringIO缓冲区,并通过子进程完全控制事情。
ThorSummoner
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.