关于linux:为什么要使用Python的os模块方法而不是直接执行shell命令?

Why use Python's os module methods instead of executing shell commands directly?

我试图了解使用Python的库函数执行特定于操作系统的任务(如创建文件/目录,更改文件属性等)而不是仅通过os.system()subprocess.call()执行这些命令的动机是什么?

例如,为什么我要使用os.chmod而不是os.system("chmod...")

我知道尽可能多地使用Python的可用库方法而不是直接执行shell命令更"pythonic"。 但是,从功能的角度来看,还有其他动机吗?

我只是在谈论在这里执行简单的单行shell命令。 当我们需要更多地控制任务的执行时,我理解使用subprocess模块更有意义,例如。


  • 它更快,os.systemsubprocess.call创建新的流程,这对于这么简单的事情是不必要的。实际上,带有shell参数的os.systemsubprocess.call通常会创建至少两个新进程:第一个是shell,第二个是您正在运行的命令(如果它不是shell构建的) -in like test)。

  • 某些命令在单独的进程中无用。例如,如果运行os.spawn("cd dir/"),它将更改子进程的当前工作目录,但不会更改Python进程的当前工作目录。您需要使用os.chdir

  • 您不必担心shell解释的特殊字符。无论文件名是什么,os.chmod(path, mode)都会起作用,而如果文件名是; rm -rf ~os.spawn("chmod 777" + path)将会失败。 (请注意,如果使用subprocess.call而不使用shell参数,则可以解决此问题。)

  • 您不必担心以破折号开头的文件名。 os.chmod("--quiet", mode)将更改名为--quiet的文件的权限,但os.spawn("chmod 777 --quiet")将失败,因为--quiet被解释为参数。即使对于subprocess.call(["chmod","777","--quiet"])也是如此。

  • 您的跨平台和跨shell问题较少,因为Python的标准库应该为您处理。你的系统有chmod命令吗?它安装了吗?它是否支持您希望它支持的参数? os模块将尝试尽可能跨平台,并在不可能时提供文档。

  • 如果您正在运行的命令具有您关心的输出,则需要解析它,这比听起来更棘手,因为您可能会忘记角落情况(文件名中包含空格,制表符和换行符),即使您不关心可移植性。


  • 它更安全。这里给出一个想法是一个示例脚本

    1
    2
    3
    import os
    file = raw_input("Please enter a file:")
    os.system("chmod 777" + file)

    如果来自用户的输入是test; rm -rf ~,那么这将删除主目录。

    这就是使用内置函数更安全的原因。

    因此,为什么你应该使用subprocess而不是system。


    在执行命令时,在os模块中使用os.systemsubprocess模块优先选择Python更具体的方法有四种情况:

    • 冗余 - 产生另一个过程是多余的,浪费时间和资源。
    • 可移植性 - os模块中的许多方法在多个平台中可用,而许多shell命令是特定于操作系统的。
    • 理解结果 - 生成执行任意命令的进程会强制您解析输出结果,并了解命令是否以及为何出错。
    • 安全性 - 进程可以执行它给出的任何命令。这是一种弱设计,可以通过使用os模块中的特定方法来避免。

    冗余(参见冗余代码):

    您实际上是在执行最终系统调用的过程中执行冗余的"中间人"(在您的示例中为chmod)。这个中间人是一个新的过程或子壳。

    来自os.system

    Execute the command (a string) in a subshell ...

    subprocess只是一个产生新进程的模块。

    您可以在不产生这些过程的情况下完成所需的操作。

    可移植性(参见源代码可移植性):

    os模块的目的是提供通用的操作系统服务,它的描述始于:

    This module provides a portable way of using operating system dependent functionality.

    您可以在Windows和unix上使用os.listdir。尝试将os.system / subprocess用于此功能将强制您维护两个调用(对于ls / dir)并检查您所使用的操作系统。这不是那么便携,以后会引起更多的挫折(参见处理输出)。

    理解命令的结果:

    假设您要列出目录中的文件。

    如果您使用os.system("ls") / subprocess.call(['ls']),则只能返回进程的输出,这基本上是带有文件名的大字符串。

    如何从两个文件中告诉一个带有空格的文件?

    如果您没有列出文件的权限怎么办?

    你应该如何将数据映射到python对象?

    这些只是我的头脑,虽然有这些问题的解决方案 - 为什么再次解决一个为你解决的问题?

    这是一个遵循不要重复自己原则(通常被称为"干")的例子,不重复已经存在且可以免费使用的实现。

    安全:

    os.systemsubprocess非常强大。当你需要这种力量时它很好,但是当你不需要它时它很危险。当您使用os.listdir时,您知道除了列出文件或引发错误之外,它不能执行任何其他操作。当您使用os.systemsubprocess来实现相同的行为时,您可能最终会做一些您不想做的事情。

    注射安全性(见壳注射实例):

    如果你使用来自用户的输入作为新命令,你基本上给了他一个shell。这很像SQL注入,在DB中为用户提供shell。

    一个例子是表单的命令:

    1
    2
    # ... read some user input
    os.system(user_input +" some continutation")

    这可以很容易地利用输入运行任意代码:NASTY COMMAND;#来创建最终的:

    1
    os.system("NASTY COMMAND; # some continuation")

    有许多此类命令可能会使您的系统面临风险。


    原因很简单 - 当你调用shell函数时,它会创建一个子shell,在命令存在后会被销毁,所以如果你在shell中更改目录 - 它不会影响Python中的环境。

    此外,创建子shell非常耗时,因此直接使用OS命令会影响您的性能

    编辑

    我有一些运行时间测试:

    1
    2
    3
    4
    5
    6
    7
    8
    In [379]: %timeit os.chmod('Documents/recipes.txt', 0755)
    10000 loops, best of 3: 215 us per loop

    In [380]: %timeit os.system('chmod 0755 Documents/recipes.txt')
    100 loops, best of 3: 2.47 ms per loop

    In [382]: %timeit call(['chmod', '0755', 'Documents/recipes.txt'])
    100 loops, best of 3: 2.93 ms per loop

    内部功能运行速度提高10倍以上

    EDIT2

    可能存在调用外部可执行文件可能比Python包产生更好结果的情况 - 我只记得我的一位同事发来的邮件,通过子进程调用的gzip的性能比他使用的Python包的性能要高得多。但当我们谈论模拟标准OS命令的标准OS包时,当然不是


    Shell调用是特定于操作系统的,而在大多数情况下,Python os模块函数不是。它避免产生子进程。


    效率更高。"shell"只是另一个包含大量系统调用的OS二进制文件。为什么只为单个系统调用产生创建整个shell进程的开销?

    当你将os.system用于不是内置shell的东西时,情况会更糟。你启动一个shell进程,然后启动一个可执行文件,然后(两个进程)进行系统调用。至少subprocess将不再需要shell中间进程。

    这不是Python特有的。 systemd是对Linux启动时间的改进,原因相同:它自己进行必要的系统调用,而不是产生一千个shell。