我试图了解使用Python的库函数执行特定于操作系统的任务(如创建文件/目录,更改文件属性等)而不是仅通过os.system()或subprocess.call()执行这些命令的动机是什么?
例如,为什么我要使用os.chmod而不是os.system("chmod...")?
我知道尽可能多地使用Python的可用库方法而不是直接执行shell命令更"pythonic"。 但是,从功能的角度来看,还有其他动机吗?
我只是在谈论在这里执行简单的单行shell命令。 当我们需要更多地控制任务的执行时,我理解使用subprocess模块更有意义,例如。
-
你基本上击中了头部。您引用的操作系统级别任务很常见,他们保证自己的功能,而不是仅仅被降级为通过os.system调用。
-
顺便说一下,你是否尝试过执行时间 - os.chmod vs. os.system("chmod ...")。我猜想它会回答你问题的一部分。
-
为什么print可以os.system("echo Hello world!")?
-
出于同样的原因,您应该使用os.path来处理路径而不是手动处理它们:它适用于运行它的每个操作系统。
-
"直接执行shell命令"实际上不那么直接。 shell不是系统的低级接口,os.chmod不会调用shell的chmod程序。使用os.system('chmod ...')启动shell来解释字符串以调用另一个可执行文件来调用C chmod函数,而os.chmod(...)更直接地转到C chmod。
-
@ user2357112:严格来说,它不一定是C的chmod,而是chmod的系统调用。在CPython的情况下,它可能不会重新实现本地的syscall约定,而只是重用C库的syscall约定实现,但其他实现可能会有不同的决定。
它更快,os.system和subprocess.call创建新的流程,这对于这么简单的事情是不必要的。实际上,带有shell参数的os.system和subprocess.call通常会创建至少两个新进程:第一个是shell,第二个是您正在运行的命令(如果它不是shell构建的) -in like test)。
某些命令在单独的进程中无用。例如,如果运行os.spawn("cd dir/"),它将更改子进程的当前工作目录,但不会更改Python进程的当前工作目录。您需要使用os.chdir。
您不必担心shell解释的特殊字符。无论文件名是什么,os.chmod(path, mode)都会起作用,而如果文件名是; rm -rf ~,os.spawn("chmod 777" + path)将会失败。 (请注意,如果使用subprocess.call而不使用shell参数,则可以解决此问题。)
您不必担心以破折号开头的文件名。 os.chmod("--quiet", mode)将更改名为--quiet的文件的权限,但os.spawn("chmod 777 --quiet")将失败,因为--quiet被解释为参数。即使对于subprocess.call(["chmod","777","--quiet"])也是如此。
您的跨平台和跨shell问题较少,因为Python的标准库应该为您处理。你的系统有chmod命令吗?它安装了吗?它是否支持您希望它支持的参数? os模块将尝试尽可能跨平台,并在不可能时提供文档。
如果您正在运行的命令具有您关心的输出,则需要解析它,这比听起来更棘手,因为您可能会忘记角落情况(文件名中包含空格,制表符和换行符),即使您不关心可移植性。
-
要添加到"跨平台"点,列出目录是Linux上的"ls",Windows上的"dir"。获取目录的内容是一项非常常见的低级任务。
-
@CortAmmon:"低级别"是相对的,ls或dir对于某些类型的开发人员来说是非常高级别的,就像bash或cmd或ksh或您喜欢的任何shell一样。
-
@phresnel:我从来没有这么想过。对我来说,"直接调用你的操作系统的内核API"是非常低级的。我假设有一个不同的观点,这是在逃避我,因为我(自然地)接近它与我自己的偏见。
-
@CortAmmon:对,ls比它高,因为它不是对你的操作系统内核API的直接调用。这是一个(小)应用程序。
-
@SteveJessop。我称"获取目录的内容"为低级别。我不是在考虑ls或dir而是opendir()/readdir()(linux api)或FindFirstFile()/FindNextFile()(windows api)或File.listFiles(java API)或Directory.GetFiles()(C#)。所有这些都与直接调用操作系统密切相关。有些可能就像将数字推入寄存器并调用int 13h来触发内核模式一样简单。
-
@CortAmmon:啊,ISWYM。我将关于"列出目录"的评论与关于"获取目录内容"的评论结合起来,假设你打算在两种情况下谈论同样的事情。但现在我意识到,该评论的一部分可能是区分两者,而不是将它们混为一谈:-)
-
坦率地说,我会把跨平台问题放在首位:This module provides a portable way... docs.python.org/2/library/os.html
它更安全。这里给出一个想法是一个示例脚本
1 2 3
| import os
file = raw_input("Please enter a file:")
os.system("chmod 777" + file) |
如果来自用户的输入是test; rm -rf ~,那么这将删除主目录。
这就是使用内置函数更安全的原因。
因此,为什么你应该使用subprocess而不是system。
-
或者另一种看待它的方法,更容易做到正确,编写Python程序或编写编写shell脚本的Python程序? :-)
-
@SteveJessop,我的一位同事惊讶于我帮他写的一个小Python脚本的工作速度提高了20(!)倍。我解释说输出重定向可能看起来很性感 - 但它需要在每次迭代时打开和关闭文件。但有些人喜欢用艰难的方式做事 - :)
-
@SteveJessop,这是一个技巧问题 - 直到运行时才会知道! :)
在执行命令时,在os模块中使用os.system或subprocess模块优先选择Python更具体的方法有四种情况:
-
冗余 - 产生另一个过程是多余的,浪费时间和资源。
-
可移植性 - os模块中的许多方法在多个平台中可用,而许多shell命令是特定于操作系统的。
-
理解结果 - 生成执行任意命令的进程会强制您解析输出结果,并了解命令是否以及为何出错。
-
安全性 - 进程可以执行它给出的任何命令。这是一种弱设计,可以通过使用os模块中的特定方法来避免。
冗余(参见冗余代码):
您实际上是在执行最终系统调用的过程中执行冗余的"中间人"(在您的示例中为chmod)。这个中间人是一个新的过程或子壳。
来自os.system:
Execute the command (a string) in a subshell ...
而subprocess只是一个产生新进程的模块。
您可以在不产生这些过程的情况下完成所需的操作。
可移植性(参见源代码可移植性):
os模块的目的是提供通用的操作系统服务,它的描述始于:
This module provides a portable way of using operating system dependent functionality.
您可以在Windows和unix上使用os.listdir。尝试将os.system / subprocess用于此功能将强制您维护两个调用(对于ls / dir)并检查您所使用的操作系统。这不是那么便携,以后会引起更多的挫折(参见处理输出)。
理解命令的结果:
假设您要列出目录中的文件。
如果您使用os.system("ls") / subprocess.call(['ls']),则只能返回进程的输出,这基本上是带有文件名的大字符串。
如何从两个文件中告诉一个带有空格的文件?
如果您没有列出文件的权限怎么办?
你应该如何将数据映射到python对象?
这些只是我的头脑,虽然有这些问题的解决方案 - 为什么再次解决一个为你解决的问题?
这是一个遵循不要重复自己原则(通常被称为"干")的例子,不重复已经存在且可以免费使用的实现。
安全:
os.system和subprocess非常强大。当你需要这种力量时它很好,但是当你不需要它时它很危险。当您使用os.listdir时,您知道除了列出文件或引发错误之外,它不能执行任何其他操作。当您使用os.system或subprocess来实现相同的行为时,您可能最终会做一些您不想做的事情。
注射安全性(见壳注射实例):
如果你使用来自用户的输入作为新命令,你基本上给了他一个shell。这很像SQL注入,在DB中为用户提供shell。
一个例子是表单的命令:
1 2
| # ... read some user input
os.system(user_input +" some continutation") |
这可以很容易地利用输入运行任意代码:NASTY COMMAND;#来创建最终的:
1
| os.system("NASTY COMMAND; # some continuation") |
有许多此类命令可能会使您的系统面临风险。
原因很简单 - 当你调用shell函数时,它会创建一个子shell,在命令存在后会被销毁,所以如果你在shell中更改目录 - 它不会影响Python中的环境。
此外,创建子shell非常耗时,因此直接使用OS命令会影响您的性能
编辑
我有一些运行时间测试:
1 2 3 4 5 6 7 8
| 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包时,当然不是
-
用iPython完成任何机会吗?不认为你可以使用普通的解释器使用以%开头的特殊函数。
-
@aPyDeveloper,是的,它是iPython - 在Ubuntu上。"神奇的"%timeit是一种祝福 - 虽然有些情况 - 主要是字符串格式化 - 它无法处理
-
或者你也可以创建一个python脚本,然后在终端输入time ,它会告诉你真实的,用户和处理时间。也就是说,如果您没有iPython并且可以访问Unix命令行。
-
@aPyDeveloper,当我在我的机器上安装iPython时,我认为没有理由努力工作
-
真正!我确实说过你没有iPython。 :)
Shell调用是特定于操作系统的,而在大多数情况下,Python os模块函数不是。它避免产生子进程。
-
Python模块函数还生成新的子进程以调用新的子shell。
-
@Koderok废话,模块函数在进程中调用
-
@Koderok:os模块使用shell命令使用的底层系统调用,它不使用shell命令。这意味着os系统调用通常比shell命令更安全,更快(没有字符串解析,boo fork,没有exec,而只是内核调用)。请注意,在大多数情况下,shell调用和系统调用通常具有相似或相同的名称,但是单独记录; shell调用在man部分1(默认man部分)中,而等效命名的系统调用在man部分2中(例如man 2 chmod)。
-
@ dwurf,LieRyan:我的坏!看来,我有一个错误的想法。谢谢!
效率更高。"shell"只是另一个包含大量系统调用的OS二进制文件。为什么只为单个系统调用产生创建整个shell进程的开销?
当你将os.system用于不是内置shell的东西时,情况会更糟。你启动一个shell进程,然后启动一个可执行文件,然后(两个进程)进行系统调用。至少subprocess将不再需要shell中间进程。
这不是Python特有的。 systemd是对Linux启动时间的改进,原因相同:它自己进行必要的系统调用,而不是产生一千个shell。