问题:为什么要使用Python的os模块方法而不是直接执行shell命令?
我试图了解使用Python的库函数执行特定于操作系统的任务(例如创建文件/目录,更改文件属性等)的动机是什么,而不是仅通过os.system()
or 来执行这些命令subprocess.call()
?
例如,为什么我要使用os.chmod
而不是做os.system("chmod...")
?
我知道,尽可能多地使用Python的可用库方法,而不是直接执行Shell命令,更像是“ Pythonic”。但是,从功能角度来看,这样做还有其他动机吗?
我只在这里谈论执行简单的单行shell命令。当我们需要对任务的执行进行更多控制时,我知道subprocess
例如使用模块更有意义。
回答 0
它速度更快,
os.system
并subprocess.call
创建了新的流程,而这对于这种简单的操作是不必要的。事实上,os.system
并subprocess.call
与shell
参数通常至少创建两个新的流程:第一个是罩,而第二个是命令,你正在运行(如果它不是内置像贝壳test
)。有些命令在单独的过程中没有用。例如,如果运行
os.spawn("cd dir/")
,它将更改子进程的当前工作目录,但不会更改Python进程的当前工作目录。您需要使用os.chdir
它。您不必担心shell 解释的特殊字符。
os.chmod(path, mode)
不管文件名是什么都可以使用,而os.spawn("chmod 777 " + path)
如果文件名是则将失败; rm -rf ~
。(请注意,如果subprocess.call
不带shell
参数使用,可以解决此问题。)您不必担心以破折号开头的文件名。
os.chmod("--quiet", mode)
将更改名为的文件的权限--quiet
,但os.spawn("chmod 777 --quiet")
会失败,因为--quiet
会解释为参数。即使这样,也是如此subprocess.call(["chmod", "777", "--quiet"])
。您可以减少跨平台和跨外壳的问题,因为Python的标准库应该可以为您解决这些问题。您的系统有
chmod
命令吗?安装好了吗?它支持您期望它支持的参数吗?该os
模块将尝试尽可能地跨平台,并在不可能的情况下进行记录。如果您正在运行的命令具有您所关心的输出,则需要对其进行解析,这比听起来要棘手,因为您可能会忘记了极端情况(其中包含空格,制表符和换行符的文件名),即使您不在乎可移植性。
回答 1
更安全。这里给你一个想法是一个示例脚本
import os
file = raw_input("Please enter a file: ")
os.system("chmod 777 " + file)
如果来自用户的输入是test; rm -rf ~
,则将删除主目录。
这就是为什么使用内置函数更安全的原因。
因此,为什么还要使用子流程而不是系统。
回答 2
在执行命令时,有四种很强的情况os
比起使用os.system
或subprocess
模块,更喜欢在模块中使用Python更具体的方法:
- 冗余 -产生另一个进程是多余的,浪费时间和资源。
- 可移植性 –
os
模块中的许多方法可在多个平台上使用,而许多shell命令是特定于OS的。 - 了解结果 -生成执行任意命令的进程会迫使您从输出中解析结果,并了解命令是否以及为什么做错了什么。
- 安全 -进程可以执行它给出的任何命令。这是一个较弱的设计,可以通过使用
os
模块中的特定方法来避免。
冗余(请参阅冗余代码):
实际上,您在执行最终系统调用的过程chmod
中正在执行一个冗余的“中间人”(在您的示例中)。这个中间人是一个新的进程或子外壳。
来自os.system
:
在子shell中执行命令(字符串)…
并且subprocess
仅仅是产生新流程的模块。
您可以执行所需的操作而无需产生这些过程。
可移植性(请参阅源代码可移植性):
该os
模块的目的是提供通用的操作系统服务,其描述始于:
该模块提供了使用依赖于操作系统的功能的便携式方法。
您可以os.system
/ subprocess
用于此功能将迫使您维护两个调用(ls
/ dir
),并检查您所使用的操作系统。这不是便携式的,以后会引起更大的挫败感(请参阅处理输出)。
了解命令的结果:
假设您要列出目录中的文件。
如果使用os.system("ls")
/ subprocess.call(['ls'])
,则只能返回该进程的输出,这基本上是一个带有文件名的大字符串。
如何从两个文件中分辨出文件名中带有空格的文件?
如果您无权列出文件怎么办?
您应该如何将数据映射到python对象?
这些只是我的头上问题,尽管有解决这些问题的方法-为什么要再次解决为您解决的问题?
这是通过不重复已经存在且可供您免费使用的实现来遵循“ 不要重复自己”原理(通常称为“ DRY”)的示例。
安全:
os.system
并且subprocess
功能强大。当您需要这种功能时,这很好,但是当您不需要这种功能时,这是危险的。使用时os.listdir
,您知道它只能执行其他操作,然后列出文件或引发错误。当您使用os.system
或subprocess
实现相同的行为时,您可能最终会做一些原本不想做的事情。
注射安全性(请参见外壳注射示例):
如果将来自用户的输入用作新命令,则基本上已经给了他一个外壳。这就像SQL注入为用户在DB中提供外壳程序一样。
一个示例将是以下形式的命令:
# ... read some user input
os.system(user_input + " some continutation")
这可以很容易利用来运行任何使用输入任意代码:NASTY COMMAND;#
创建最终的:
os.system("NASTY COMMAND; # some continuation")
有许多这样的命令会使您的系统处于危险之中。
回答 3
出于简单的原因-当您调用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软件包时肯定不会
回答 4
Shell调用是特定于OS的,而在大多数情况下不是Python os模块的功能。并且避免产生子流程。
回答 5
效率更高。“ shell”只是另一个OS二进制文件,其中包含许多系统调用。为什么只为单个系统调用而产生创建整个Shell进程的开销?
当您使用os.system
的不是内置shell 时,情况甚至更糟。您启动一个Shell进程,然后启动一个可执行文件,然后该可执行文件(两个进程分开)进行系统调用。至少subprocess
可以消除对shell中介过程的需求。
这不是特定于Python的。systemd
出于相同的原因,它大大缩短了Linux启动时间:它使必要的系统调用本身而不是产生一千个shell。