为什么使用Python的os模块方法而不是直接执行shell命令?
我想了解使用Python的库函数执行特定于操作系统的任务(如创build文件/目录,更改文件属性等)的动机,而不是仅仅通过os.system()
或os.system()
subprocess.call()
?
例如,我为什么要使用os.chmod
而不是做os.system("chmod...")
?
我明白,尽可能多地使用Python的可用库方法,而不是直接执行shell命令,是更“pythonic”的。 但是,从functionangular度来看,还有其他动机吗?
我只是在说这里执行简单的单行shell命令。 当我们需要更多的控制任务的执行时,我明白使用subprocess
进程模块更有意义。
-
它更快 ,
os.system
和os.system
创build新的进程,这是不必要的东西这么简单。 实际上,带有shell
参数的os.system
和os.system
通常会创build至less两个新进程:第一个是shell,第二个是您正在运行的命令(如果它不是内置的shell像test
)。 -
有些命令在单独的进程中是无用的 。 例如,如果运行
os.spawn("cd dir/")
,它将更改subprocess的当前工作目录,但不会更改Python进程的当前工作目录。 你需要使用os.chdir
。 -
您不必担心由shell 解释的特殊字符 。
os.chmod(path, mode)
无论文件名是什么都可以工作,而os.spawn("chmod 777 " + path)
将会失败,如果文件名是类似的; rm -rf ~
; rm -rf ~
。 (请注意,如果使用不带shell
参数的subprocess.call
则可以解决此问题。) -
您不必担心以短划线开头的文件名 。
os.chmod("--quiet", mode)
会改变名为--quiet
的文件的权限,但os.spawn("chmod 777 --quiet")
会失败,因为--quiet
被解释为一个参数。 即使对于subprocess.call(["chmod", "777", "--quiet"])
。 -
您对跨平台和跨shell的关注较less,因为Python的标准库应该为您处理。 你的系统有
chmod
命令吗? 是否安装? 它是否支持您期望它支持的参数?os
模块将尝试尽可能跨平台,并在不可能的时候logging文件。 -
如果你正在运行的命令有你所关心的输出 ,那么你需要对它进行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.system
或os.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.system
和subprocess
os.system
是强大的。 当你需要这个权力的时候是好的,但是当你不这样做的时候是危险的。 当你使用os.listdir
,你知道它不能做任何其他事情,然后列出文件或引发错误。 当你使用os.system
或os.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。