在Python中运行Bash命令

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 / < >


不要使用os.system。它已被弃用,取而代之的是子流程。来自文档:"此模块打算替换几个旧的模块和功能:os.systemos.spawn

就像你的情况:

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=Trueshell=False的含义,以及它如何改变报价和shell便利的可用性。
  • 了解sh和bash的区别
  • 了解子流程如何与父流程分离,并且通常无法更改父流程。
  • 避免将python解释器作为python的子进程运行。

下面将详细介绍这些主题。好的。倾向于subprocess.run()subprocess.check_call()

subprocess.Popen()函数是一个低级的工作程序,但很难正确使用,最终会复制/粘贴多行代码…它作为一组高级包装函数方便地存在于标准库中,用于各种用途,下面将详细介绍。好的。

以下是文档中的一段:好的。

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 underlying Popen 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与subprocess.Popen()的比较

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

如果您只需要一个程序来运行并将控制权返回给python,那么subprocess.run()是一种可行的方法。对于更复杂的场景(后台进程,可能是与python父程序的交互I/O),您仍然需要使用subprocess.Popen()并自己处理所有管道。这需要对所有运动部件有一个相当复杂的理解,不应轻率地进行。较简单的Popen对象表示(可能仍在运行)进程,在子进程的剩余生命周期内,需要从代码中管理该进程。好的。

也许应该强调的是,仅仅是subprocess.Popen()就仅仅创造了一个过程。如果您不这样做,您将有一个子进程与Python同时运行,因此是一个"后台"进程。如果它不需要进行输入、输出或与您协调,那么它可以与您的Python程序并行执行有用的工作。好的。避开os.system()os.popen()

自Time Eternal(好吧,从python 2.5开始)以来,os模块文档中就包含了倾向于subprocess而不是os.system()的建议:好的。

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.

system()的问题在于它明显依赖于系统,不提供与子流程交互的方式。它只运行,标准输出和标准错误超出了Python的范围。Python接收到的唯一信息是命令的退出状态(零表示成功,但非零值的含义也与系统有关)。好的。

PEP-324(上文已经提到)包含了更详细的理由,说明了为什么os.system存在问题以及subprocess如何试图解决这些问题。好的。

过去,os.popen()甚至更强烈地反对:好的。

Deprecated since version 2.6: This function is obsolete. Use the subprocess module.

Ok.

但是,从Python3的某个时候开始,它被重新实现为只使用subprocess,并重定向到subprocess.Popen()文档以获取详细信息。好的。了解并经常使用check=True

您还会注意到,subprocess.call()os.system()有许多相同的限制。在常规使用中,您通常应该检查流程是否成功完成,subprocess.check_call()subprocess.check_output()执行此操作(后者还返回已完成子流程的标准输出)。同样,除非您特别需要允许子进程返回错误状态,否则通常应将check=Truesubprocess.run()一起使用。好的。

实际上,对于check=Truesubprocess.check_*,如果子进程返回非零退出状态,python将抛出CalledProcessError异常。好的。

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

另一方面,check_call()check_output()的一个常见问题是,当异常出现时(例如,grep没有找到匹配项),盲目使用这些功能的用户会感到惊讶。(您可能应该用原生python代码替换grep,如下所述。)好的。

所有计算在内的东西,您需要了解shell命令如何返回退出代码,以及在什么条件下返回非零(错误)退出代码,并有意识地决定应该如何正确地处理它。好的。理解并可能使用text=True又名universal_newlines=True

由于python 3,python内部的字符串是unicode字符串。但不能保证子进程生成Unicode输出或字符串。好的。

(如果差异不明显,建议使用Ned Batchelder的实用Unicode,如果不是完全强制的,则阅读。如果您愿意,链接后面有一个36分钟的视频演示,不过您自己阅读页面的时间可能会少得多。)好的。

在底层,python必须获取一个bytes缓冲区并以某种方式解释它。如果它包含一个二进制数据块,那么就不应该将其解码为Unicode字符串,因为这是一种容易出错和导致错误的行为——这正是困扰许多python 2脚本的那种令人讨厌的行为,而在此之前,还没有一种正确区分编码文本和二进制数据的方法。好的。

使用text=True时,您会告诉python,实际上,您希望系统默认编码中的文本数据能够返回,并且应该尽可能将其解码为python(unicode)字符串(通常在任何中等更新的系统上都是utf-8,除了Windows?)好的。

如果这不是您所要求的,python只会在stdoutstderr字符串中提供bytes字符串。也许在以后的某个时候,你知道它们毕竟是文本字符串,你也知道它们的编码方式。然后,你可以解码它们。好的。

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为关键字参数引入了较短、更具描述性和可理解的别名text,该参数以前被错误地称为universal_newlines。好的。了解shell=Trueshell=False的关系

使用shell=True时,只需向shell传递一个字符串,shell就会从中获取字符串。好的。

使用shell=False,您可以绕过shell向操作系统传递参数列表。好的。

当您没有shell时,您将保存一个进程,并消除相当大的隐藏复杂性,这可能或可能不存在漏洞,甚至安全问题。好的。

另一方面,如果没有shell,就没有重定向、通配符扩展、作业控制和大量其他shell功能。好的。

一个常见的错误是使用shell=True,然后仍然向python传递一个令牌列表,反之亦然。在某些情况下,这是可行的,但实际上定义不清,可能会以有趣的方式中断。好的。

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或sed脚本应该简单地转换为python。好的。

为了部分地说明这一点,这里有一个典型但有点愚蠢的例子,它涉及许多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进行读取,并将其内容作为标准输入传递给grepoutputfile的标准输出随后落在outputfile中。这通常不难用本机python代码替换。
  • 管道是重定向的一种形式。echo foo | nl运行两个子进程,其中echo的标准输出是nl的标准输入(在操作系统级别,在类Unix系统中,这是一个文件句柄)。如果您不能用本机python代码替换管道的一端或两端,那么可以考虑使用shell,尤其是当管道有两个或三个以上的进程时(尽管可以查看python标准库中的pipes模块或一些更现代、更通用的第三方竞争对手)。
  • 作业控制允许您中断作业、在后台运行它们、将它们返回到前台等等。停止和继续进程的基本UNIX信号当然也可以从python获得。但是,作业是shell中的一个更高级的抽象,它涉及流程组等,如果您想从Python中执行类似的操作,就必须理解这些抽象。

了解sh和bash的区别

subprocess使用/bin/sh运行shell命令,除非您特别要求(当然,在Windows上除外,它使用COMSPEC变量的值)。这意味着各种仅bash的功能,如数组、[[等,不可用。好的。

如果需要只使用bash语法,则可以将路径作为executable='/bin/bash'传递给shell(当然,如果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')

subprocess与其母公司分离,不能更改。

一个常见的错误是好的。

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

(更不用说明显的重构subprocess.run(['echo', 'bar']);但是echo一开始就是在子流程中运行的一个糟糕的例子,当然了)。好的。不要从python运行python

这是一个有点可疑的建议;在某些情况下,它确实是有意义的,甚至是将Python解释器作为来自Python脚本的子进程运行的绝对要求。但通常,正确的方法只是简单地将另一个python模块引入调用脚本并直接调用其函数。好的。

如果另一个python脚本在您的控制之下,并且它不是一个模块,那么考虑将其转换为一个模块。(这个答案已经太长了,所以我不会在这里深入探讨细节。)好的。

如果需要并行,可以使用multiprocessing模块在子流程中运行python函数。还有一个threading,它在一个进程中运行多个任务(它更轻,给您更多的控制权,但也更受限于进程中的线程是紧密耦合的,并且绑定到单个gil)。好的。好啊。


用子进程调用它

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的包。这个/usr/bin/cwm需要它。如果您在Ubuntu/Debian上,请使用aptitude安装python-swap


要在没有shell的情况下运行该命令,请将该命令作为列表传递,并使用[subprocess]在python中实现重定向:

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)

注:末尾无> test.ntstdout=file实现了重定向。

要在python中使用shell运行命令,请将该命令作为字符串传递并启用shell=True

1
2
3
4
5
#!/usr/bin/env python
import subprocess

subprocess.check_call("cwm --rdf test.rdf --ntriples > test.nt",
                      shell=True)

这里是shell负责输出重定向的地方(> test.nt在命令中)。

要运行使用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


做这个的方法是使用subprocess.Popen

subprocess.Popen采用一个列表,其中第一个元素是要运行的命令,后跟任何命令行参数。

举个例子:

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