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


157

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

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

我知道,尽可能多地使用Python的可用库方法,而不是直接执行Shell命令,更像是“ Pythonic”。但是,从功能角度来看,这样做还有其他动机吗?

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


6
你基本上打在了头上。您所指的操作系统级别的任务很常见,以至于它们保证了自己的功能,而不仅仅是通过os.system进行调用。
deweyredman

7
顺便说一句,您是否尝试计时执行时间-os.chmodos.system(“ chmod ...”)。我可能会猜测它会回答您部分问题。
火山

61
为什么要什么print时候可以os.system("echo Hello world!")呢?
user253751

25
出于相同的原因,您应该使用os.path路径而不是手动处理路径:它可在运行该路径的每个OS上使用。
Bakuriu 2015年

51
实际上,“直接执行shell命令” 不太直接。Shell不是系统的底层接口,os.chmod也不会调用chmodShell会调用的程序。使用os.system('chmod ...')启动外壳程序以解释字符串以调用另一个可执行文件以调用C chmod函数,而os.chmod(...)直接执行C 调用chmod
user2357112支持Monica 2015年

Answers:


325
  1. 速度更快os.systemsubprocess.call创建了新的流程,而这对于这种简单的操作是不必要的。事实上,os.systemsubprocess.callshell参数通常至少创建两个新的流程:第一个是罩,而第二个是命令,你正在运行(如果它不是内置像贝壳test)。

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

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

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

  5. 您可以减少跨平台和跨外壳的问题,因为Python的标准库应该可以为您解决这些问题。您的系统有chmod命令吗?安装好了吗?它支持您期望它支持的参数吗?该os模块将尝试尽可能地跨平台,并在不可能的情况下进行记录。

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


38
要添加到“跨平台”点,在Linux上列出的目录是“ ls”,在Windows上列出的目录是“ dir”。获取目录的内容是非常常见的低级任务。
Cort Ammon

1
@CortAmmon:“低水平”是相对的,ls或者dir是相当高的水平,某些类型的开发者,就如同bashcmdksh或任何外壳您喜欢的。
塞巴斯蒂安·马赫2015年

1
@phresnel:我从来没有那样想过。对我来说,“直接调用您操作系统的内核API”的级别很低。我假设对此有不同的看法,因为我(自然地)以自己的偏见来接近它。
Cort Ammon

5
@CortAmmon:ls是的,它的级别更高,因为它不是对操作系统内核API的直接调用。这是一个(小型)应用程序。
史蒂夫·杰索普

1
@SteveJessop。我把“获取目录的内容”称为低级。我没有想到ls还是dir而是opendir()/readdir()(Linux的API)或FindFirstFile()/FindNextFile()(的Windows API)或File.listFiles(Java API)或Directory.GetFiles()(C#)。所有这些都与直接调用操作系统紧密相关。有些可能很简单,例如将数字推入寄存器并调用int 13h以触​​发内核模式。
Cort Ammon

133

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

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

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

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

因此,为什么还要使用子流程而不是系统。


26
还是用另一种方式看待它,编写Python程序或编写编写Shell脚本的Python程序更容易实现?:-)
史蒂夫·杰索普

3
我的一位同事@SteveJessop惊讶于我帮助他编写的一个小的Python脚本比tan shell脚本快20倍(!)。我解释说,输出重定向可能看起来很性感-但这需要在每次迭代中打开和关闭文件。但有些人喜欢用硬方法做事-:)
火山

1
@SteveJessop,这是一个技巧性的问题-您要到运行时才知道!:)

60

在执行命令时,有四种很强的情况os比起使用os.systemsubprocess模块,更喜欢在模块中使用Python更具体的方法:

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

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

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

来自os.system

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

并且subprocess仅仅是产生新流程的模块。

您可以执行所需的操作而无需产生这些过程。

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

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

该模块提供了使用依赖于操作系统的功能的便携式方法。

您可以os.listdir在Windows和Unix上使用。尝试将os.system/ subprocess用于此功能将迫使您维护两个调用(ls/ dir),并检查您所使用的操作系统。这不是便携式的,以后引起更大的挫败感(请参阅处理输出)。

了解命令的结果:

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

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

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

如果您无权列出文件怎么办?

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

这些只是我的头上问题,尽管有解决这些问题的方法-为什么要再次解决为您解决的问题?

这是通过重复已经存在且可供您免费使用的实现来遵循“ 不要重复自己”原理(通常称为“ DRY”)的示例。

安全:

os.system并且subprocess功能强大。当您需要这种功能时,这很好,但是当您不需要这种功能时,这是危险的。使用时os.listdir,您知道它只能执行其他操作,然后列出文件或引发错误。当您使用os.systemsubprocess实现相同的行为时,您可能最终会做一些原本不想做的事情。

注射安全性(请参见外壳注射示例

如果将来自用户的输入用作新命令,则基本上已经给了他一个外壳。这就像SQL注入为用户在DB中提供外壳程序一样。

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

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

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

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

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


3
我会说2.是主要原因。
jaredad7

23

出于简单的原因-当您调用shell函数时,它将创建一个子shell,该子shell在命令存在后会被破坏,因此,如果您在shell中更改目录,则不会影响您在Python中的环境。

此外,创建子外壳非常耗时,因此直接使用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倍以上

编辑2

在某些情况下,调用外部可执行文件可能比Python软件包产生更好的结果-我刚刚记得我的一位同事发送的一封邮件,其中说通过子进程调用的gzip的性能比他使用的Python软件包的性能高得多。但是当我们谈论模拟标准OS命令的标准OS软件包时肯定不会


借助iPython可以做到吗?没想到您可以使用从%正常解释器开始的特殊功能。
iProgram 2015年

@aPyDeveloper,是的,它是iPython-在Ubuntu上。“神奇的” %timeit是一种祝福-尽管在某些情况下-大多数采用字符串格式-无法处理
火山

1
或者,您也可以制作一个python脚本,然后键入time <path to script> terminal,它将告诉您所花费的真实,用户和处理时间。也就是说,如果您没有iPython并且可以访问Unix命令行。
iProgram 2015年

1
@aPyDeveloper,我认为没有理由要努力工作-当我在计算机上安装iPython时
火山

真正!我确实说过,如果您没有iPython。:)
iProgram 2015年

16

Shell调用是特定于OS的,而在大多数情况下不是Python os模块的功能。并且避免产生子流程。


1
Python模块函数还产生新的子进程来调用新的子shell。
Koderok

7
@Koderok废话,模块功能称为进程内
dwurf 2015年

3
@Koderok:os模块使用shell命令使用的基础系统调用,而不使用shell命令。这意味着OS系统调用通常比shell命令更安全,更快捷(没有字符串解析,嘘声叉,没有exec,而是一个内核调用)。请注意,在大多数情况下,shell调用和系统调用通常具有相似或相同的名称,但分别记录在案;shell调用位于man部分1(默认的man部分)中,而同名的系统调用位于man部分2中(例如man 2 chmod)。
Lie Ryan

1
@ dwurf,LieRyan:我不好!看来我有一个错误的观念。谢谢!
Koderok'2

11

效率更高。“ shell”只是另一个OS二进制文件,其中包含许多系统调用。为什么只为单个系统调用而产生创建整个Shell进程的开销?

当您使用os.system的不是内置shell 时,情况甚至更糟。您启动一个Shell进程,然后启动一个可执行文件,然后该可执行文件(两个进程分开)进行系统调用。至少subprocess可以消除对shell中介过程的需求。

这不是特定于Python的。systemd出于相同的原因,它大大缩短了Linux启动时间:它使必要的系统调用本身而不是产生一千个shell。

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.