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

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

我想了解使用 Python 的库函数执行 OS 特定任务(例如创建 files/directories、更改文件属性等)背后的动机是什么。而不是仅通过 os.system()subprocess.call()?

执行这些命令

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

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

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

执行命令时,在 os module over using os.system or the subprocess 模块中优先使用 Python 的更具体的方法有四个强有力的案例:

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

冗余(参见 redundant code):

您实际上在执行最终系统调用的过程中执行了冗余 "middle-man"(在您的示例中为 chmod)。这个中间人是新进程还是子进程shell.

来自 os.system:

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

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

您可以在不产生这些进程的情况下执行您需要的操作。

可移植性(参见 source code portability):

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

开头

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

您可以在 windows 和 unix 上使用 os.listdir。尝试使用 os.system / subprocess 来实现此功能将迫使您保持两次调用(针对 ls / dir)并检查您使用的操作系统。这不是可移植的,并且 稍后导致更多的挫败感(参见 处理输出 )。

了解命令的结果:

补充os您想要列出目录中的文件。

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

如何区分名称中带有 space 的文件和两个文件?

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

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

这些只是我的想法,虽然有解决这些问题的方法 - 为什么要再次解决已经为您解决的问题?

这是遵循 Don't Repeat Yourself 原则(通常称为 "DRY")的示例 而不是 重复已经存在且可自由使用的实现有空。

安全:

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

注射安全(见shell injection examples:

如果您将用户的输入用作新命令,您基本上已经给了他 shell。这很像 SQL 注入,在数据库中为用户提供 shell。

一个示例是以下形式的命令:

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

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

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

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

原因很简单 - 当您调用 shell 函数时,它会创建一个子 shell,它会在您的命令存在后被销毁,因此如果您更改 [=37= 中的目录] - 它不会影响您在 Python.

中的环境

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

编辑

我做了一些计时测试运行:

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 命令

比较安全。这里给你一个想法是一个示例脚本

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

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

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

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

Shell 调用是 OS 特定的,而 Python os 模块函数不是,在 most 的情况下。它避免产生子进程。

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

当您将 os.system 用于非 shell 内置的内容时,情况会更糟。您启动一个 shell 进程,该进程又启动一个可执行文件,然后(两个进程之外)进行系统调用。至少 subprocess 会消除对 shell 中间过程的需要。

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

  1. 更快 os.systemsubprocess.call 创建新进程,这对于如此简单的事情来说是不必要的。事实上,带有 shell 参数的 os.systemsubprocess.call 通常至少会创建两个新进程:第一个是 shell,第二个是您要执行的命令're 运行ning(如果它不是像 test 那样的 shell 内置)。

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

  3. 您不必担心特殊 字符会被 shell 解释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. 也是如此
  6. 你有更少的跨平台和跨shell问题,因为Python的标准库应该处理给你的。你的系统有chmod命令吗?安装了吗?它是否支持您期望它支持的参数? os 模块将尝试尽可能跨平台,并在不可能时记录。

  7. 如果你运行ning的命令有你关心的输出,你需要解析它,这比它更棘手听起来,因为即使您不关心可移植性,您也可能会忘记极端情况(其中包含空格、制表符和换行符的文件名)。