Running Bash commands in Python
在我的本地机器,我跑的Python脚本,这contains线 P / < >
1 2 | bashCommand ="cwm --rdf test.rdf --ntriples > test.nt" os.system(bashCommand) |
这工作很好。 P / < >
然后我运行相同的代码在服务器和我得到的以下错误消息 P / < >
1 2 3 4 5 | 'import site' failed; use -v for traceback Traceback (most recent call last): File"/usr/bin/cwm", line 48, in <module> from swap import diag ImportError: No module named swap |
所以我做的,然后也inserted)的"bashcommand打印",打印比我的命令在终端之前,它runs它与os.system()。 P / < >
当然,我得到了同步误差(引起由os.system(bashcommand)),但在它打印错误的命令在终端。然后我就copied,输出和厌氧的拷贝的贴到"终端和信仰的"进入"和它的作品。 P / < >
确实没有任何人有线索怎么了? P / < >
不要使用
就像你的情况:
1 2 3 4 | bashCommand ="cwm --rdf test.rdf --ntriples > test.nt" import subprocess process = subprocess.Popen(bashCommand.split(), stdout=subprocess.PIPE) output, error = process.communicate() |
为了在前面的回答上有所扩展,这里有一些通常被忽略的细节。好的。
- 与
subprocess.check_call() 相比,subprocess.run() 更喜欢subprocess.check_call() 和朋友,而不是subprocess.call() 而不是subprocess.Popen() 而不是os.system() 而不是os.popen() 。 - 理解并可能使用
text=True ,又名universal_newlines=True 。 - 了解
shell=True 或shell=False 的含义,以及它如何改变报价和shell便利的可用性。 - 了解
sh 和bash的区别 - 了解子流程如何与父流程分离,并且通常无法更改父流程。
- 避免将python解释器作为python的子进程运行。
下面将详细介绍这些主题。好的。倾向于
以下是文档中的一段:好的。
The recommended approach to invoking subprocesses is to use the
run() function for all use cases it can handle. For more advanced use cases, the underlyingPopen interface can be used directly.Ok.
不幸的是,这些包装函数的可用性在不同的Python版本之间有所不同。好的。
- 在python3.5中正式引入了
subprocess.run() 。它旨在取代以下所有内容。 - python2.7/3.1中引入了
subprocess.check_output() 。基本上相当于subprocess.run(..., check=True, stdout=subprocess.PIPE).stdout 。 - python2.5中引入了
subprocess.check_call() 。基本上相当于subprocess.run(..., check=True) 。 subprocess.call() 在python2.4中的原始subprocess 模块(pep-324)中引入。基本上相当于subprocess.run(...).returncode 。
高级API与
重构和扩展的
如果您只需要一个程序来运行并将控制权返回给python,那么
也许应该强调的是,仅仅是
自Time Eternal(好吧,从python 2.5开始)以来,
The
subprocess module provides more powerful facilities for spawning new processes and retrieving their results; using that module is preferable to using this function.Ok.
PEP-324(上文已经提到)包含了更详细的理由,说明了为什么
过去,
Deprecated since version 2.6: This function is obsolete. Use the
subprocess module.Ok.
但是,从Python3的某个时候开始,它被重新实现为只使用
您还会注意到,
实际上,对于
另一方面,
所有计算在内的东西,您需要了解shell命令如何返回退出代码,以及在什么条件下返回非零(错误)退出代码,并有意识地决定应该如何正确地处理它。好的。理解并可能使用
由于python 3,python内部的字符串是unicode字符串。但不能保证子进程生成Unicode输出或字符串。好的。
(如果差异不明显,建议使用Ned Batchelder的实用Unicode,如果不是完全强制的,则阅读。如果您愿意,链接后面有一个36分钟的视频演示,不过您自己阅读页面的时间可能会少得多。)好的。
在底层,python必须获取一个
使用
如果这不是您所要求的,python只会在
1 2 3 4 5 6 7 8 9 10 11 | normal = subprocess.run([external, arg], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, text=True) print(normal.stdout) convoluted = subprocess.run([external, arg], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True) # You have to know (or guess) the encoding print(convoluted.stdout.decode('utf-8')) |
python 3.7为关键字参数引入了较短、更具描述性和可理解的别名
使用
使用
当您没有shell时,您将保存一个进程,并消除相当大的隐藏复杂性,这可能或可能不存在漏洞,甚至安全问题。好的。
另一方面,如果没有shell,就没有重定向、通配符扩展、作业控制和大量其他shell功能。好的。
一个常见的错误是使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | # XXX AVOID THIS BUG buggy = subprocess.run('dig +short stackoverflow.com') # XXX AVOID THIS BUG TOO broken = subprocess.run(['dig', '+short', 'stackoverflow.com'], shell=True) # XXX DEFINITELY AVOID THIS pathological = subprocess.run(['dig +short stackoverflow.com'], shell=True) correct = subprocess.run(['dig', '+short', 'stackoverflow.com'], # Probably don't forget these, too check=True, text=True) # XXX Probably better avoid shell=True # but this is nominally correct fixed_but_fugly = subprocess.run('dig +short stackoverflow.com', shell=True, # Probably don't forget these, too check=True, text=True) |
通常的反驳"但对我有用"不是一个有用的反驳,除非你确切地理解在什么情况下它可以停止工作。好的。重构示例
通常,shell的特性可以替换为本机Python代码。简单的awk或
为了部分地说明这一点,这里有一个典型但有点愚蠢的例子,它涉及许多shell特性。好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | cmd = '''while read -r x; do ping -c 3"$x" | grep 'round-trip min/avg/max' done <hosts.txt''' # Trivial but horrible results = subprocess.run( cmd, shell=True, universal_newlines=True, check=True) print(results.stdout) # Reimplement with shell=False with open('hosts.txt') as hosts: for host in hosts: host = host.rstrip(' ') # drop newline ping = subprocess.run( ['ping', '-c', '3', host], text=True, stdout=subprocess.PIPE, check=True) for line in ping.stdout.split(' '): if 'round-trip min/avg/max' in line: print('{}: {}'.format(host, line)) |
这里需要注意的一些事项:好的。
- 对于
shell=False ,您不需要shell需要字符串的引号。无论如何,引号可能是一个错误。 - 在子流程中尽可能少地运行代码通常是有意义的。这使您可以从Python代码中对执行进行更多的控制。
- 尽管如此,复杂的shell管道是乏味的,有时在Python中重新实现是很困难的。
重构后的代码还用非常简洁的语法说明了shell到底为您做了多少工作——不管是好是坏。python说explicit比implicit好,但是python代码相当冗长,可以说看起来比实际复杂。另一方面,它提供了许多点,在这些点中,您可以在其他东西的中间获取控制权,这是一个很普通的例子,通过增强,我们可以轻松地将主机名和shell命令输出包括在一起。(在壳牌公司,这也不是一个挑战,而是以牺牲另一个转移和可能另一个过程为代价。)好的。通用外壳结构
为了完整性起见,这里简要解释了这些shell特性中的一些,以及一些关于如何用本机Python工具替换它们的注释。好的。
- globbing又名通配符扩展,可以用
glob.glob() 替换,也可以经常用简单的python字符串比较(如for file in os.listdir('.'): if not file.endswith('.png'): continue )替换。bash还有各种其他扩展设施,如.{png,jpg} brace扩展和{1..100} 以及tilde扩展(~ 扩展到您的主目录,更常见的是~account 扩展到另一个用户的主目录) - 重定向允许您将文件作为标准输入进行读取,并将标准输出写入文件。
grep 'foo' 打开outputfile outputfile 进行写入,inputfile 进行读取,并将其内容作为标准输入传递给grep ,outputfile 的标准输出随后落在outputfile 中。这通常不难用本机python代码替换。 - 管道是重定向的一种形式。
echo foo | nl 运行两个子进程,其中echo 的标准输出是nl 的标准输入(在操作系统级别,在类Unix系统中,这是一个文件句柄)。如果您不能用本机python代码替换管道的一端或两端,那么可以考虑使用shell,尤其是当管道有两个或三个以上的进程时(尽管可以查看python标准库中的pipes 模块或一些更现代、更通用的第三方竞争对手)。 - 作业控制允许您中断作业、在后台运行它们、将它们返回到前台等等。停止和继续进程的基本UNIX信号当然也可以从python获得。但是,作业是shell中的一个更高级的抽象,它涉及流程组等,如果您想从Python中执行类似的操作,就必须理解这些抽象。
了解
如果需要只使用bash语法,则可以将路径作为
1 2 3 4 5 6 7 8 | subprocess.run(''' # This for loop syntax is Bash only for((i=1;i<=$#;i++)); do # Arrays are Bash-only array[i]+=123 done''', shell=True, check=True, executable='/bin/bash') |
一个常见的错误是好的。
1 2 | subprocess.run('foo=bar', shell=True) subprocess.run('echo"$foo"', shell=True) # Doesn't work |
除了缺乏优雅之外,这也显示出对"子流程"这一名称的"子"部分根本缺乏理解。好的。
子进程与python完全分离,当它完成时,python不知道它做了什么(除了可以从子进程的退出状态和输出中推断出的模糊指示器)。子级通常不能更改父级的环境;它不能设置变量、更改工作目录,或者,换句话说,如果没有父级的合作,它就不能与父级进行通信。好的。
在这种特殊情况下,最直接的解决方法是在单个子进程中运行这两个命令;好的。
1 | subprocess.run('foo=bar; echo"$foo"', shell=True) |
尽管很明显这个特殊的用例根本不需要shell。记住,您可以通过好的。
1 | os.environ['foo'] = 'bar' |
或将环境设置传递给具有好的。
1 | subprocess.run('echo"$foo"', shell=True, env={'foo': 'bar'}) |
(更不用说明显的重构
这是一个有点可疑的建议;在某些情况下,它确实是有意义的,甚至是将Python解释器作为来自Python脚本的子进程运行的绝对要求。但通常,正确的方法只是简单地将另一个python模块引入调用脚本并直接调用其函数。好的。
如果另一个python脚本在您的控制之下,并且它不是一个模块,那么考虑将其转换为一个模块。(这个答案已经太长了,所以我不会在这里深入探讨细节。)好的。
如果需要并行,可以使用
用子进程调用它
1 2 | import subprocess subprocess.Popen("cwm --rdf test.rdf --ntriples > test.nt") |
您得到的错误似乎是因为服务器上没有交换模块,您应该在服务器上安装交换,然后再次运行脚本。
可以使用bash程序和参数-c执行命令:
1 2 | bashCommand ="cwm --rdf test.rdf --ntriples > test.nt" output = subprocess.check_output(['bash','-c', bashCommand]) |
你可以使用"子流程",但我总是觉得这不是一种"Python式"的方式。所以我创建了sultan(不知羞耻的plug),它可以很容易地运行命令行函数。
https://github.com/aeroxis/sultan
根据错误,您在服务器上缺少名为swap的包。这个
要在没有shell的情况下运行该命令,请将该命令作为列表传递,并使用
1 2 3 4 5 6 | #!/usr/bin/env python import subprocess with open('test.nt', 'wb', 0) as file: subprocess.check_call("cwm --rdf test.rdf --ntriples".split(), stdout=file) |
注:末尾无
要在python中使用shell运行命令,请将该命令作为字符串传递并启用
1 2 3 4 5 | #!/usr/bin/env python import subprocess subprocess.check_call("cwm --rdf test.rdf --ntriples > test.nt", shell=True) |
这里是shell负责输出重定向的地方(
要运行使用bashims的bash命令,请显式指定bash可执行文件,例如,要模拟bash进程替换:
1 2 3 4 5 | #!/usr/bin/env python import subprocess subprocess.check_call('program <(command) <(another-command)', shell=True, executable='/bin/bash') |
也可以使用"os.popen"。例子:
1 2 3 4 5 | import os command = os.popen('ls -al') print(command.read()) print(command.close()) |
输出:
1 2 3 4 5 6 7 | total 16 drwxr-xr-x 2 root root 4096 ago 13 21:53 . drwxr-xr-x 4 root root 4096 ago 13 01:50 .. -rw-r--r-- 1 root root 1278 ago 13 21:12 bot.py -rw-r--r-- 1 root root 77 ago 13 21:53 test.py None |
做这个的方法是使用
举个例子:
1 2 3 4 5 6 7 | import subprocess args = ['echo', 'Hello!'] subprocess.Popen(args) // same as running `echo Hello!` on cmd line args2 = ['echo', '-v', '"Hello Again"'] subprocess.Popen(args2) // same as running 'echo -v"Hello Again!"` on cmd line |