在我的本地机器上,我运行一个包含这一行的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

因此,我所做的是,我插入了一个打印bashCommand,它在终端中使用os.system()运行命令之前,将我打印出来。

当然,我再次得到错误(由os.system(bashCommand)引起),但在该错误之前,它在终端中打印命令。然后我复制输出并复制粘贴到终端,然后按enter,它工作了…

有人知道这是怎么回事吗?


用subprocess调用它

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

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


不要使用os.system。它已被弃用,而支持subprocess。文档中写道:“这个模块打算取代几个旧的模块和函数:os。系统,os.spawn”。

比如你的例子:

import subprocess

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

根据错误,您在服务器上丢失了一个名为swap的包。这个/usr/bin/cwm需要它。如果你使用的是Ubuntu/Debian,使用aptitude安装python-swap。


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

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

你可以使用子进程,但我总觉得这不是一种“python”的方式。所以我创建了Sultan(无耻的插头),它可以很容易地运行命令行函数。

https://github.com/aeroxis/sultan


python的方法是使用subprocess。Popen

子流程。Popen接受一个列表,其中第一个元素是要运行的命令,后面跟着任何命令行参数。

举个例子:

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

你也可以使用'os.popen'。 例子:

import os

command = os.popen('ls -al')
print(command.read())
print(command.close())

输出:

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

为了进一步扩展前面的答案,这里有一些通常被忽略的细节。

Prefer subprocess.run() over subprocess.check_call() and friends over subprocess.call() over subprocess.Popen() over os.system() over os.popen() Understand and probably use text=True, aka universal_newlines=True. Understand the meaning of shell=True or shell=False and how it changes quoting and the availability of shell conveniences. Understand differences between sh and Bash Understand how a subprocess is separate from its parent, and generally cannot change the parent. Avoid running the Python interpreter as a subprocess of Python.

下面将更详细地讨论这些主题。

首选subprocess.run()或subprocess.check_call()

subprocess.Popen()函数是一个低级的工作工具,但正确使用它很棘手,你最终会复制/粘贴多行代码……它们已经作为一组用于各种目的的高级包装器函数存在于标准库中,下面将更详细地介绍。

以下是文档中的一段:

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

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

subprocess.run() was officially introduced in Python 3.5. It is meant to replace all of the following. subprocess.check_output() was introduced in Python 2.7 / 3.1. It is basically equivalent to subprocess.run(..., check=True, stdout=subprocess.PIPE).stdout subprocess.check_call() was introduced in Python 2.5. It is basically equivalent to subprocess.run(..., check=True) subprocess.call() was introduced in Python 2.4 in the original subprocess module (PEP-324). It is basically equivalent to subprocess.run(...).returncode

高级API vs子进程。popen ()

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

subprocess.run() is the way to go if you simply need a program to run and return control to Python. For more involved scenarios (background processes, perhaps with interactive I/O with the Python parent program) you still need to use subprocess.Popen() and take care of all the plumbing yourself. This requires a fairly intricate understanding of all the moving parts and should not be undertaken lightly. The simpler Popen object represents the (possibly still-running) process which needs to be managed from your code for the remainder of the lifetime of the subprocess.

应该强调的是,subprocess.Popen()仅仅创建了一个进程。如果就这样,就会有一个子进程与Python同时运行,也就是“后台”进程。如果它不需要输入或输出,或以其他方式与你协调,它可以与你的Python程序并行地做有用的工作。

避免使用os.system()和os.popen()

从time eternal(好吧,从Python 2.5开始)开始,os模块文档就包含了优先使用subprocess而不是os.system()的建议:

子进程模块为生成新进程和检索其结果提供了更强大的功能;使用该模块比使用此函数更可取。

system()的问题是它明显依赖于系统,并且没有提供与子进程交互的方法。它只是简单地运行,带有Python无法触及的标准输出和标准错误。Python接收到的唯一信息是命令的退出状态(零表示成功,尽管非零值的含义也有点依赖于系统)。

PEP-324(上面已经提到过)包含了为什么os。系统有问题,以及子进程如何尝试解决这些问题。

Os.popen()曾经被强烈反对:

2.6版后已移除:此函数已过时。使用子流程模块。

然而,在Python 3中,它被重新实现为简单地使用subprocess,并重定向到subprocess. popen()文档以获取详细信息。

理解并经常使用check=True

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

在实践中,使用check=True或subprocess。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的《Pragmatic Unicode》,如果不是必须要读的话。如果你愿意,链接后面有一个36分钟的视频演示,不过自己阅读页面可能会花更少的时间。)

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

使用text=True,您告诉Python,实际上您希望返回系统默认编码的文本数据,并且应该尽Python的最大能力将其解码为Python (Unicode)字符串(在任何最新的系统上通常是UTF-8,除了Windows?)

如果这不是你请求回的内容,Python会在stdout和stderr字符串中给你bytes字符串。也许在以后的某个时候,您确实知道它们毕竟是文本字符串,并且知道它们的编码。然后,你可以解码它们。

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为关键字参数引入了更短、更描述性和更容易理解的别名文本,以前有些误导地称为universal_newlines。

理解shell=True vs shell=False

使用shell=True,您将单个字符串传递给shell, shell将从那里获取它。

shell=False可以绕过shell,将参数列表传递给操作系统。

当您没有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)

常见的反驳“但它对我有用”并不是一个有用的反驳,除非你确切地了解在什么情况下它会停止工作。

简单回顾一下,正确的用法是这样的

subprocess.run("string for 'the shell' to parse", shell=True)
# or
subprocess.run(["list", "of", "tokenized strings"]) # shell=False

如果您希望避免使用shell,但又太懒或不确定如何将字符串解析为令牌列表,请注意shlex.split()可以为您完成此工作。

subprocess.run(shlex.split("no string for 'the shell' to parse"))  # shell=False
# equivalent to
# subprocess.run(["no", "string", "for", "the shell", "to", "parse"])

常规的split()在这里不起作用,因为它没有保留引号。在上面的例子中,注意“shell”是一个单独的字符串。

重构的例子

通常情况下,shell的特性可以用本地Python代码替换。简单的Awk或sed脚本应该被翻译成Python。

为了部分说明这一点,这里有一个典型但略显愚蠢的示例,其中涉及许多shell特性。

cmd = '''while read -r x;
   do ping -c 3 "$x" | grep '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 'min/avg/max' in line:
                 print('{}: {}'.format(host, line))

这里有几点需要注意:

使用shell=False时,你不需要shell要求的字符串周围的引号。无论如何加引号都可能是错误的。 在子进程中运行尽可能少的代码通常是有意义的。这使您可以从Python代码中更好地控制执行。 话虽如此,复杂的shell管道是乏味的,有时在Python中重新实现具有挑战性。

The refactored code also illustrates just how much the shell really does for you with a very terse syntax -- for better or for worse. Python says explicit is better than implicit but the Python code is rather verbose and arguably looks more complex than this really is. On the other hand, it offers a number of points where you can grab control in the middle of something else, as trivially exemplified by the enhancement that we can easily include the host name along with the shell command output. (This is by no means challenging to do in the shell, either, but at the expense of yet another diversion and perhaps another process.)

通用Shell结构

为了完整起见,这里简要说明了其中一些shell特性,并说明了如何用本地Python工具替换它们。

Globbing aka wildcard expansion can be replaced with glob.glob() or very often with simple Python string comparisons like for file in os.listdir('.'): if not file.endswith('.png'): continue. Bash has various other expansion facilities like .{png,jpg} brace expansion and {1..100} as well as tilde expansion (~ expands to your home directory, and more generally ~account to the home directory of another user) Shell variables like $SHELL or $my_exported_var can sometimes simply be replaced with Python variables. Exported shell variables are available as e.g. os.environ['SHELL'] (the meaning of export is to make the variable available to subprocesses -- a variable which is not available to subprocesses will obviously not be available to Python running as a subprocess of the shell, or vice versa. The env= keyword argument to subprocess methods allows you to define the environment of the subprocess as a dictionary, so that's one way to make a Python variable visible to a subprocess). With shell=False you will need to understand how to remove any quotes; for example, cd "$HOME" is equivalent to os.chdir(os.environ['HOME']) without quotes around the directory name. (Very often cd is not useful or necessary anyway, and many beginners omit the double quotes around the variable and get away with it until one day ...) Redirection allows you to read from a file as your standard input, and write your standard output to a file. grep 'foo' <inputfile >outputfile opens outputfile for writing and inputfile for reading, and passes its contents as standard input to grep, whose standard output then lands in outputfile. This is not generally hard to replace with native Python code. Pipelines are a form of redirection. echo foo | nl runs two subprocesses, where the standard output of echo is the standard input of nl (on the OS level, in Unix-like systems, this is a single file handle). If you cannot replace one or both ends of the pipeline with native Python code, perhaps think about using a shell after all, especially if the pipeline has more than two or three processes (though look at the pipes module in the Python standard library or a number of more modern and versatile third-party competitors). Job control lets you interrupt jobs, run them in the background, return them to the foreground, etc. The basic Unix signals to stop and continue a process are of course available from Python, too. But jobs are a higher-level abstraction in the shell which involve process groups etc which you have to understand if you want to do something like this from Python. Quoting in the shell is potentially confusing until you understand that everything is basically a string. So ls -l / is equivalent to 'ls' '-l' '/' but the quoting around literals is completely optional. Unquoted strings which contain shell metacharacters undergo parameter expansion, whitespace tokenization and wildcard expansion; double quotes prevent whitespace tokenization and wildcard expansion but allow parameter expansions (variable substitution, command substitution, and backslash processing). This is simple in theory but can get bewildering, especially when there are several layers of interpretation (a remote shell command, for example).

理解sh和Bash之间的区别

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

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

子进程与其父进程是分开的,并且不能更改它

一个比较常见的错误是做一些像

subprocess.run('cd /tmp', shell=True)
subprocess.run('pwd', shell=True)  # Oops, doesn't print /tmp

如果第一个子进程试图设置一个环境变量,也会发生同样的事情,当然,当您运行另一个子进程时,该环境变量将消失,等等。

子进程完全独立于Python运行,当它完成时,Python不知道它做了什么(除了它可以从退出状态和子进程的输出推断出的模糊指示)。孩子通常不能改变父母的环境;如果没有父进程的合作,它不能设置变量、更改工作目录,或者简单地说,不能与父进程通信。

在这种特殊情况下,立即解决的方法是在单个子进程中运行这两个命令;

subprocess.run('cd /tmp; pwd', shell=True)

虽然很明显这个特殊的用例不是很有用;相反,在运行子进程之前使用CWD关键字参数,或者简单地使用os.chdir()。类似地,为了设置一个变量,您可以通过操作当前进程(以及它的子进程)的环境

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

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

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

(更不用说重构subprocess.run(['echo', 'bar']);当然,echo首先是在子进程中运行的一个糟糕的例子)。

不要从Python运行Python

这是一个有点可疑的建议;当然,在某些情况下,将Python解释器作为Python脚本的子进程运行是有意义的,甚至是绝对必要的。但通常情况下,正确的方法是将另一个Python模块导入调用脚本并直接调用它的函数。

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

如果需要并行性,可以使用multiprocessing模块在子进程中运行Python函数。还有在一个进程中运行多个任务的线程(它更轻量级,给你更多的控制,但也有更多的约束,因为进程中的线程是紧密耦合的,并绑定到单个GIL)。


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

#!/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)

注意:没有>测试。Nt在最后。文件实现重定向。


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

#!/usr/bin/env python
import subprocess

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

这里是shell负责输出重定向(>测试。Nt在命令中)。


要运行使用bashisms的bash命令,需要显式指定bash可执行文件,例如,模拟bash进程替换:

#!/usr/bin/env python
import subprocess

subprocess.check_call('program <(command) <(another-command)',
                      shell=True, executable='/bin/bash')

subprocess.Popen()优于os.system(),因为它提供了更多的控制和可见性。但是,如果你觉得subprocess.Popen()太啰嗦或复杂,可以使用我在上面写的一个小包装器——peasyshell,它可以很容易地与Python中的bash交互。

https://github.com/davidohana/peasyshell


复制粘贴:

def run_bash_command(cmd: str) -> Any:
    import subprocess

    process = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)
    output, error = process.communicate()
    if error:
        raise Exception(error)
    else:
        return output