在Python中运行Bash命令

mkn 260 python bash

在我的本地机器上,我运行一个包含这一行的python脚本

bashCommand = "cwm --rdf test.rdf --ntriples > test.nt"
os.system(bashCommand)

这很好用.

然后我在服务器上运行相同的代码,我收到以下错误消息

'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

所以我所做的就是插入一个"print bashCommand",它在使用os.system()运行它之前打印我,而不是终端中的命令.

当然,我再次得到错误(由os.system(bashCommand)引起)但在该错误之前它在终端中打印命令.然后我只是复制了那个输出并在终端上做了一个复制粘贴,然后点击回车就可以了......

有没有人知道发生了什么?

user225312.. 285

不要用os.system.它已被弃用,有利于进程.从文档:"这个模块打算替换几个较旧的模块和功能:os.system,os.spawn".

就像你的情况一样:

bashCommand = "cwm --rdf test.rdf --ntriples > test.nt"
import subprocess
process = subprocess.Popen(bashCommand.split(), stdout=subprocess.PIPE)
output, error = process.communicate()

  • @AWrightIV如果你需要在特定的工作目录中运行你的子进程,你可以使用`cwd`参数给Popen:`subprocess.Popen(...,cwd ='path\to\somewhere')` (31认同)
  • 当我需要做一个`cd'path\to\somewhere'`后跟另一个需要在某个地方运行的bash命令时,这没有做我想要的.@ user225312 (8认同)
  • 对于我的命令,我需要shell = True; http://stackoverflow.com/questions/18962785/oserror-errno-2-no-such-file-or-directory-while-using-python-subprocess-in-dj (6认同)
  • 在这种情况下,最好使用shlex.split()而不是string.split() (4认同)
  • ...(`stdout = file`在这种情况下将输出重定向到文件.它实现了`> file`).在期望重定向的最后一个命令中传递`...,'>','file']`是错误的(如果没有shell,它将无法工作,如果你使用shell,你应该将命令作为一个串) (3认同)
  • 代码是错误的`&gt; file`语法(输出重定向)需要一个shell,`split()`试图将`'&gt;','file'作为参数传递给`cwm`程序本身。使用shell并将命令作为* string *传递:`subprocess.check_call(shell_command,shell = True)`或直接在没有外壳的情况下运行命令(默认)*并将命令行参数作为* list *传递。在这种情况下,您需要在Python中实现重定向(和其他shell功能),例如,将`with open('test.nt','wb',0)作为文件:subprocess.check_call(['cwm','- rdf','test.rdf','-ntriples'],stdout = file)`... (2认同)

tripleee.. 112

为了稍微扩展这里的早期答案,有许多细节常被忽视.

  • 身高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的子进程运行.

下面将详细介绍这些主题.

喜欢subprocess.run()subprocess.check_call()

subprocess.Popen()函数是一个低级别的主力,但是使用正确并且你最终复制/粘贴多行代码......这很方便地已经存在于标准库中,作为一组用于各种目的的高级包装函数,以下更详细地介绍.

这是文档中的一段:

调用子进程的推荐方法是将run()函数用于它可以处理的所有用例.对于更高级的用例,Popen可以直接使用底层接口.

不幸的是,这些包装函数的可用性在Python版本之间有所不同.

  • subprocess.run()在Python 3.5中正式引入.它旨在取代以下所有内容.
  • subprocess.check_output()在Python 2.7/3.1中引入.它基本上相当于subprocess.run(..., check=True, stdout=subprocess.PIPE).stdout
  • subprocess.check_call()是在Python 2.5中引入的.它基本上相当于subprocess.run(..., check=True)
  • subprocess.call()在原始subprocess模块(PEP-324)中引入了Python 2.4 .它基本上相当于subprocess.run(...).returncode

高级API vs. subprocess.Popen()

重构和扩展subprocess.run()比它取代的旧的遗留功能更具逻辑性和更多功能.它返回一个CompletedProcess对象,该对象具有各种方法,允许您从完成的子流程中检索退出状态,标准输出以及一些其他结果和状态指示器.

subprocess.run()如果你只是需要一个程序来运行并将控制权交还给Python,那就是你要走的路.对于更复杂的场景(后台进程,可能还有使用Python父程序的交互式I/O),您仍然需要自己使用subprocess.Popen()并处理所有管道.这需要对所有运动部件进行相当复杂的理解,不应轻易进行.更简单的Popen对象表示(可能仍在运行)进程,需要在代码中管理子进程的剩余生命周期.

或许应该强调的是,subprocess.Popen()仅仅创造一个过程.如果你把它留在那,你有一个与Python同时运行的子进程,所以一个"后台"进程.如果它不需要输入或输出或以其他方式与您协调,它可以与您的Python程序并行执行有用的工作.

避免os.system()os.popen()

由于时间永恒的(当然,因为Python 2.5)的os模块文件包含了更倾向于推荐subprocessos.system():

subprocess模块提供了更强大的工具来产生新流程并检索其结果; 使用该模块比使用此功能更可取.

问题system()在于它显然依赖于系统,并且不提供与子进程交互的方法.它只是运行,标准输出和标准错误超出了Python的范围.Python收到的唯一信息是命令的退出状态(零表示成功,尽管非零值的含义也在某种程度上取决于系统).

PEP-324(上面已经提到过)包含了更详细的理由,说明为什么os.system有问题以及如何subprocess解决这些问题.

os.popen()曾经更加强烈劝阻:

从版本2.6开始不推荐使用:此功能已过时.使用该subprocess模块.

但是,从Python 3的某个时候开始,它已重新实现,只需使用subprocess,并重定向到subprocess.Popen()文档以获取详细信息.

理解并经常使用 check=True

你还会注意到它subprocess.call()有许多相同的限制os.system().在常规使用中,通常应该检查进程是否成功完成,哪个subprocess.check_call()和哪个subprocess.check_output()(后者也返回完成的子进程的标准输出).同样,除非特别需要允许子进程返回错误状态check=True,subprocess.run()否则通常应该使用.

实际上,使用check=True或者subprocess.check_*,如果子进程返回非零退出状态,Python将抛出CalledProcessError异常.

如果子进程失败,下游代码失败时,常见的错误subprocess.run()是省略check=True并感到惊讶.

在另一方面,有一个共同的问题check_call()check_output()是谁盲目使用这些功能的用户是当异常发生,例如当惊讶grep没有找到匹配.(grep无论如何,您应该用原生Python代码替换,如下所述.)

所有事情都计算在内,您需要了解shell命令如何返回退出代码,以及在什么条件下它们将返回非零(错误)退出代码,并有意识地决定应该如何处理它.

理解并可能使用text=Trueakauniversal_newlines=True

从Python 3开始,Python内部的字符串是Unicode字符串.但是不能保证子进程会生成Unicode输出或字符串.

(如果差异不是很明显,建议使用Ned Batchelder的实用Unicode,如果不是直接强制性的话,请阅读.如果您愿意,可以在链接后面进行36分钟的视频演示,尽管自己阅读页面可能会花费更少的时间. )

在内心深处,Python必须获取bytes缓冲区并以某种方式解释它.如果它包含一大堆二进制数据,则不应将其解码为Unicode字符串,因为这是容易出错和导致错误的行为 - 恰恰是那种在许多Python 2脚本中出现问题的麻烦行为.正确区分编码文本和二进制数据.

有了text=True,你告诉Python你实际上期望在系统的默认编码中支持文本数据,并且它应该被解码成Python(Unicode)字符串,以达到Python的最佳能力(通常是UTF-8,适用于任何适度的日期系统,除了Windows?)

如果这是不是你要求什么时候回来,Python将只是给你bytes的字符串stdoutstderr字符串.也许稍后您知道它们毕竟是文本字符串,并且您知道它们的编码.然后,您可以解码它们.

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 text为关键字参数引入了更短,更具描述性和可理解性的别名,这在以前有些误导性地被称为universal_newlines.

了解shell=Truevsshell=False

有了shell=True你一个字符串传递给你的壳,壳从那里拿过来.

随着shell=False你传递的参数列表的OS,绕过外壳.

当你没有shell时,你可以保存一个进程并消除相当多的隐藏复杂性,这可能会或可能不会存在错误甚至是安全问题.

另一方面,当您没有shell时,您没有重定向,通配符扩展,作业控制和大量其他shell功能.

一个常见的错误是使用shell=True然后仍然向Python传递令牌列表,反之亦然.这种情况在某些情况下会起作用,但实际上定义不明确,可能会以有趣的方式破解.

# 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或sed脚本应该简单地转换为Python.

为了部分地说明这一点,这里是一个典型但略显愚蠢的例子,涉及许多shell特征.

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('\n')  # drop newline
        ping = subprocess.run(
             ['ping', '-c', '3', host],
             text=True,
             stdout=subprocess.PIPE,
             check=True)
        for line in ping.stdout.split('\n'):
             if 'round-trip min/avg/max' in line:
                 print('{}: {}'.format(host, line))

有些事情需要注意:

  • 随着shell=False你不需要引用的外壳需要大约字符串.无论如何放置引号可能是一个错误.
  • 在子进程中尽可能少地运行代码通常是有意义的.这使您可以从Python代码中更好地控制执行.
  • 话虽如此,复杂的shell管道很繁琐,有时候很难在Python中重新实现.

重构的代码还用非常简洁的语法说明了shell为你做了多少 - 无论好坏.蟒蛇说明确优于隐式,但Python代码相当冗长,可以说是看起来复杂得多,这确实是.另一方面,它提供了许多要点,您可以在其他内容中获取控制权,正如我们可以轻松地包含主机名和shell命令输出的增强功能所示.(这绝不是在shell中做的挑战,而是以另一个转移和另一个过程为代价.)

常见Shell构造

为了完整起见,这里有一些shell功能的简要说明,以及有关如何用原生Python工具替换它们的一些注释.

  • Globbing aka通配符扩展可以替换为glob.glob()或通常使用简单的Python字符串比较for file in os.listdir('.'): if not file.endswith('.png'): continue.Bash有各种其他扩展工具,如.{png,jpg}大括号扩展和{1..100}波浪扩展(~扩展到您的主目录,更一般~account地扩展到另一个用户的主目录)
  • 重定向允许您从文件中读取标准输入,并将标准输出写入文件.$SHELL打开$my_exported_var用于写入和os.environ['SHELL']读取,并将其内容作为标准输入传递给export,其标准输出随后进入env=.这通常很难用原生Python代码替换.
  • 管道是一种重定向形式.subprocess运行两个子进程,其中标准输出shell=False是标准输入cd "$HOME"(在OS级别,在类Unix系统中,这是单个文件句柄).如果你不能用本机Python代码替换管道的一端或两端,或许考虑使用shell,特别是如果管道有两个或三个以上的进程(虽然查看os.chdir(os.environ['HOME'])Python标准库中的模块或数字更现代和多才多艺的第三方竞争对手).
  • 作业控制允许您中断作业,在后台运行它们,将它们返回到前台等.当然,也可以从Python获得停止并继续进程的基本Unix信号.但是,作业是shell中的更高级抽象,涉及进程组等,如果你想从Python做这样的事情,你必须要理解.

了解cd和Bash 之间的差异

grep 'foo' <inputfile >outputfileoutputfile除非你特别要求,否则运行你的shell命令(除了当然在Windows上,它使用inputfile变量的值).这意味着不能使用各种类似Bash的功能,如数组grep.

如果您需要使用仅Bash语法,则可以将路径传递给shell outputfile(当然,如果您的Bash安装在其他地方,则需要调整路径).

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')

A echo foo | nl与其父项分开,无法更改

一个常见的错误是做类似的事情

subprocess.run('foo=bar', shell=True)
subprocess.run('echo "$foo"', shell=True)  # Doesn't work

除了缺乏优雅之外,还缺乏对"子过程"这个名称的"子"部分的基本缺乏理解.

子进程完全独立于Python运行,当它完成时,Python不知道它做了什么(除了它可以从退出状态和子进程的输出推断出的模糊指示).孩子一般不能改变父母的环境; 它不能设置变量,更改工作目录,或者用很多词来与父节点进行通信,而无需与父节点协作.

在这种特殊情况下,立即修复是在单个子进程中运行这两个命令;

subprocess.run('foo=bar; echo "$foo"', shell=True)

虽然这个特殊用例显然根本不需要shell.请记住,您可以通过操纵当前进程(以及它的子进程)的环境

os.environ['foo'] = 'bar'

或者将环境设置传递给子进程

subprocess.run('echo "$foo"', shell=True, env={'foo': 'bar'})

(更不用说显而易见的重构echo;但nl当然首先是在子进程中运行的一个糟糕的例子).

不要从Python运行Python

这是一个有点可疑的建议; 在某些情况下,它确实有意义,或者甚至是将Python解释器作为Python脚本的子进程运行的绝对要求.但很多时候,正确的方法是pipes将其他Python模块简单地放入调用脚本并直接调用其函数.

如果其他Python脚本在您的控制之下,并且它不是模块,请考虑将其转换为一个模块.(这个答案已经太久了,所以我不会在这里深入研究细节.)

如果需要并行性,可以使用ls -l /模块在子进程中运行Python函数. 还有'ls' '-l' '/'一个在单个进程中运行多个任务(它更轻量级并且为您提供更多控制,但在进程中的线程紧密耦合并绑定到单个GIL时也更受限制.)

  • 令我难以置信的是,我必须对这样一个基本问题发布一个新答案,以便展示如何从问题中运行命令.你的答案很长,但我没有看到这样的例子.不相关:避免货物结果.如果check_call()适用于您的情况,请使用它.我不得不修复一个盲目使用`run()`的代码.缺少`check = True`会导致一个错误,如果使用了check_call就会避免 - 名称中有"check",你不能丢失它 - 这是正确的默认值:不要无声地忽略错误.我没有进一步阅读. (3认同)
  • 有关如何避免将Python作为子进程调用的更详细说明,请参见[此答案在切向相似的问题上。](/ a / 48401774/874188) (2认同)

Jakob Bowyer.. 38

用子进程调用它

import subprocess
subprocess.Popen("cwm --rdf test.rdf --ntriples > test.nt")

你得到的错误似乎是因为服务器上没有交换模块,你应该在服务器上安装swap然后再次运行脚本

  • `swap`模块显然在那里,因为从shell运行命令工作. (3认同)
  • 不在服务器上,当他在服务器上运行它时会出现导入错误. (2认同)

小智.. 17

您可以使用bash程序,使用参数-c执行命令:

bashCommand = "cwm --rdf test.rdf --ntriples > test.nt"
output = subprocess.check_output(['bash','-c', bashCommand])

  • `subprocess.check_output(bashCommand,shell = True)`做同样的事情.如果您的命令是静态字符串,请尝试自己将其解析为列表并避免使用`shell = True`; 虽然在这种情况下你无论如何都需要shell进行重定向,否则你需要将它重构为纯Python - 用open('test.nt','w')作为dest:output = subprocess.check_output([ 'cwm',' - rdf','test.rdf',' - ntriples'],stdout = dest,shell = False)` (2认同)

小智.. 9

你可以使用'subprocess',但我一直觉得这不是'Pythonic'的做法.所以我创建了Sultan(无耻插件),可以轻松运行命令行功能.

https://github.com/aeroxis/sultan

  • 做得好!比子流程更清晰,更直观. (2认同)

kichik.. 7

根据错误,您缺少服务器上名为swap的包.这/usr/bin/cwm需要它.如果您使用的是Ubuntu/Debian,请python-swap使用aptitude进行安装.


归档时间:

查看次数:

471055 次

最近记录:

7 月,2 周 前