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

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

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

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

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

  1. 更快os.systemos.system创build新的进程,这是不必要的东西这么简单。 实际上,带有shell参数的os.systemos.system通常会创build至less两个新进程:第一个是shell,第二个是您正在运行的命令(如果它不是内置的shell像test )。

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

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

  4. 您不必担心以短划线开头的文件名os.chmod("--quiet", mode)会改变名为--quiet的文件的权限,但os.spawn("chmod 777 --quiet")会失败,因为--quiet被解释为一个参数。 即使对于subprocess.call(["chmod", "777", "--quiet"])

  5. 您对跨平台和跨shell的关注较less,因为Python的标准库应该为您处理。 你的系统有chmod命令吗? 是否安装? 它是否支持您期望它支持的参数? os模块将尝试尽可能跨平台,并在不可能的时候logging文件。

  6. 如果你正在运行的命令有你所关心的输出 ,那么你需要对它进行parsing,这比你听起来更棘手,因为你可能会忘记angular落案例(包含空格,制表符和换行符的文件名),即使你不关心可移植性。

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

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

如果来自用户的input是test; rm -rf ~ test; rm -rf ~这会删除主目录。

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

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

在执行一个命令时, os模块中使用os.systemos.system模块时,有os.system情况可以使用Python的更具体的方法:

  • 冗余 – 产生另一个过程是多余的,浪费时间和资源。
  • 可移植性os模块中的许多方法在多个平台上可用,而许多shell命令是特定于os的。
  • 理解结果 – 产生执行任意命令的过程会强制你parsing输出的结果,并理解命令是否为什么做了错误。
  • 安全 – 一个进程可以执行任何命令。 这是一个弱devise,可以通过在os模块中使用特定的方法来避免。

冗余(请参阅冗余代码 ):

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

os.system

在子shell中执行命令(一个string)…

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

你可以做你所需要的,而不会产生这些过程。

可移植性(请参阅源代码可移植性 ):

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

该模块提供了一种使用与操作系统相关的function的便携方式。

你可以在windows和unix上使用os.listdir 。 尝试使用os.system / os.system这个function会迫使你保持两个调用( ls / dir )并检查你正在使用哪个操作系统。 这不是可移植的, 稍后会导致更多的挫折(请参阅处理输出 )。

了解命令的结果:

假设你想列出目录中的文件。

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

你怎么能从两个文件中分配一个名字空间的文件?

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

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

这些只是我头顶的问题,虽然有解决这些问题的办法 – 为什么又要解决一个为你解决的问题?

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

安全:

os.systemsubprocess os.system是强大的。 当你需要这个权力的时候是好的,但是当你不这样做的时候是危险的。 当你使用os.listdir ,你知道它不能做任何其他事情,然后列出文件或引发错误。 当你使用os.systemos.system来实现相同的行为时,你可能会做一些你不想做的事情。

注射安全性(参见壳注射实例

如果你使用用户的input作为一个新的命令,你基本上给了他一个shell。 这非常类似于在数据库中为用户提供一个shell的SQL注入。

一个例子是一个命令的forms:

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

这可以很容易地利用input: NASTY COMMAND;#来创build最终的:

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

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

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

另外,创build子shell是耗时的,所以直接使用OS命令会影响你的性能

编辑

我有一些时间testing运行:

 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包的性能。 但是当我们谈论模拟标准操作系统命令的标准操作系统软件包时,肯定不行

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

这是更有效率。 “shell”只是另一个包含大量系统调用的OS二进制文件。 为什么会招致创build整个shell进程只是为了这个单一的系统调用的开销?

当你使用os.system的东西不是内置的shell的情况下更糟糕。 您启动一个shell进程,然后启动一个可执行文件,然后(两个进程)进行系统调用。 至lesssubprocess进程已经消除了对shell中介进程的需求。

这不是特定于Python的。 systemd对Linux启动时间的改进是出于同样的原因:它使得必要的系统调用本身,而不是产生一千个shell。