为什么使用 Python 的 OS 模块方法而不是直接执行 shell 命令?

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

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

我理解尽可能多地使用 Python 的可用库方法比直接执行 shell 命令更“ Python 化”。但是,从功能的角度来看,这样做背后还有其他动机吗?

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

27915 次浏览

在执行命令时,与使用 os.systemsubprocess模块相比,在 os模块中更喜欢 Python 的更具体的方法,这里有四个强有力的例子:

  • 冗余 -产生另一个进程是冗余的,会浪费时间和资源。
  • 可移植性 -os模块中的许多方法可以在多个平台上使用,而许多 shell 命令是特定于 OS 的。
  • 理解结果 -生成一个进程来执行任意的命令会迫使您解析输出中的结果,并理解 如果为什么命令做错了什么。
  • Safety -进程可以潜在地执行它所给出的任何命令。这是一个薄弱的设计,它可以通过在 os模块中使用特定的方法来避免。

冗余(见 冗余代码) :

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

来自 os.system:

在子 shell 中执行命令(字符串) ..。

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

您可以在不产生这些进程的情况下完成您需要的任务。

便携性(见 < em > 源代码可移植性 ) :

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

这个模块提供了一种使用操作系统相关功能的可移植方法。

您可以在 windows 和 unix 上使用 os.listdir。尝试使用 os.system/subprocess来实现这个功能将强制您维护两个调用(对于 ls/dir)并检查所使用的操作系统。这是不可移植的和 威尔导致更多的挫折后来(见 处理输出)。

理解命令的结果:

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

如果使用 os.system("ls")/subprocess.call(['ls']),则只能获得进程的输出,这基本上是一个包含文件名的大字符串。

如何从两个文件中分辨出一个文件名中有空格的文件?

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

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

这些只是我头脑中的想法,虽然这些问题都有解决方案,但为什么要再次解决一个已经为你解决的问题呢?

这是一个遵循 不要重复自己原则(通常称为“ DRY”)的例子,通过 没有重复一个已经存在并且可以免费获得的实现。

安全:

os.systemsubprocess是强大的。当你需要这种力量的时候是好事,但是当你不需要的时候就很危险了。当您使用 os.listdir时,您的 知道不能执行其他任何操作,然后列出文件或引发错误。当你使用 os.system或者 subprocess来达到同样的行为时,你可能最终会做一些你不想做的事情。

注射安全(见 < em > 外壳注入示例 ) :

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

一个例子是表单的命令:

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

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

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

有许多这样的命令可能会使您的系统处于危险之中。

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

此外,创建子 shell 非常耗时,因此直接使用操作系统命令会影响性能

剪辑

我进行了一些计时测试:

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倍以上

编辑2

可能有些情况下,调用外部可执行文件可能会产生比 Python 包更好的结果——我刚想起我的一个同事发来的一封邮件,通过 subprocess 调用的 Gzip的性能比他使用的 Python 包的性能要高得多。但是当我们谈论仿效标准操作系统命令的标准操作系统包时,肯定不会这样

这样更安全。这里给你一个示例脚本

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

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

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

因此,您也应该使用子进程而不是系统。

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

这样效率更高。“ shell”只是另一个包含大量系统调用的 OS 二进制文件。为什么要为一个系统调用创建整个 shell 进程?

当您使用 os.system来处理不是内置 shell 的内容时,情况会更糟。您启动一个 shell 进程,该进程又启动一个可执行文件,然后(两个进程之外)进行系统调用。至少 subprocess不再需要 shell 中间进程。

这不是 Python 的专利。出于同样的原因,systemd对 Linux 启动时间的改进是如此之大: 它使必要的系统调用自己,而不是产生一千个 shell。

  1. 它的 再快点os.systemsubprocess.call创建新的过程,这是不必要的东西这么简单。实际上,带有 shell参数的 os.systemsubprocess.call通常至少创建两个新进程: 第一个进程是 shell,第二个进程是正在运行的命令(如果它不是像 test那样的 shell 内置进程的话)。

  2. 有些命令是 在另一个过程中没有用。例如,如果您运行 os.spawn("cd dir/"),它将更改子进程的当前工作目录,但不会更改 Python 进程的当前值。这需要使用 os.chdir

  3. 您不必担心特殊的 字符解释的外壳。无论文件名是什么,os.chmod(path, mode)都能正常工作,而如果文件名类似于 ; rm -rf ~os.spawn("chmod 777 " + path)就会严重失败。(注意,如果使用没有 shell参数的 subprocess.call,可以绕过这个问题。)

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

  5. 您对 跨平台和跨 shell 的关注较少,因为 Python 的标准库应该为您处理这些问题。你的系统有 chmod命令吗?装好了吗?它是否支持您期望它支持的参数?os模块将尝试尽可能跨平台和文档,当这是不可能的。

  6. 如果您正在运行的命令包含您关心的 输出,那么您需要解析它,这比听起来要复杂,因为您可能会忘记角落大小写(包含空格、制表符和换行符的文件名) ,即使您并不关心可移植性。