如何执行程序或调用系统命令?

Posted

技术标签:

【中文标题】如何执行程序或调用系统命令?【英文标题】:How to execute a program or call a system command? 【发布时间】:2010-09-10 11:51:11 【问题描述】:

如何从 Python 脚本中调用外部命令(就像我在 Unix shell 或 Windows 命令提示符下输入的一样)?

【问题讨论】:

我不明白,import os; os.system('pip list | grep anatome') 有什么问题?至少这允许您像我的示例所示那样进行管道处理。目前尚不清楚如何使用import subprocess; subprocess.run(["ls", "-l"]) 【参考方案1】:

2015 年更新:Python 3.5 添加了subprocess.run,它比 subprocess.Popen 更易于使用。我建议这样做。

>>> subprocess.run(["ls", "-l"])  # doesn't capture output
CompletedProcess(args=['ls', '-l'], returncode=0)

>>> subprocess.run("exit 1", shell=True, check=True)
Traceback (most recent call last):
  ...
subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1

>>> subprocess.run(["ls", "-l", "/dev/null"], capture_output=True)
CompletedProcess(args=['ls', '-l', '/dev/null'], returncode=0,
stdout=b'crw-rw-rw- 1 root root 1, 3 Jan 23 16:23 /dev/null\n', stderr=b'')

【讨论】:

Deprecated 不仅意味着“不再开发”,还意味着“不鼓励您使用它”。不推荐使用的功能可能随时中断,可能随时删除,或者可能很危险。你不应该在重要的代码中使用它。弃用只是比立即删除功能更好的方法,因为它让程序员有时间适应和替换他们弃用的功能。 只是为了证明我的观点:“自 2.6 版起已弃用:命令模块已在 Python 3 中删除。改用 subprocess 模块。” 不危险! Python 开发人员只注意在主要版本之间(即 2.x 和 3.x 之间)中断功能。自 2004 年的 Python 2.4 以来,我一直在使用 commands 模块。它现在在 Python 2.7 中也一样。 危险,我并不是说它可以随时被删除(这是一个不同的问题),我也没有说使用这个特定模块是危险的。但是,如果发现安全漏洞但模块没有进一步开发或维护,则可能会变得危险。 (我不想说这个模块是否容易受到安全问题的影响,只是在谈论一般已弃用的东西)【参考方案2】:

调用外部程序的方法总结,包括它们的优缺点:

    os.system 将命令和参数传递给系统的 shell。这很好,因为您实际上可以以这种方式一次运行多个命令并设置管道和输入/输出重定向。例如:

    os.system("some_command < input_file | another_command > output_file")  
    

    然而,虽然这很方便,但您必须手动处理 shell 字符(例如空格等)的转义。另一方面,这也允许您运行只是 shell 命令而不是实际外部程序的命令。

    os.popen 将做与os.system 相同的事情,只是它为您提供了一个类似文件的对象,您可以使用该对象来访问该进程的标准输入/输出。 popen 还有 3 个其他变体,它们对 i/o 的处理都略有不同。如果您将所有内容都作为字符串传递,那么您的命令将传递给 shell;如果您将它们作为列表传递,那么您无需担心转义任何内容。示例:

    print(os.popen("ls -l").read())
    

    subprocess.Popen。这旨在替代os.popen,但缺点是由于过于全面而稍微复杂一些。例如,你会说:

    print subprocess.Popen("echo Hello World", shell=True, stdout=subprocess.PIPE).stdout.read()
    

    而不是

    print os.popen("echo Hello World").read()
    

    但是很高兴将所有选项放在一个统一的类中,而不是 4 个不同的 popen 函数。见the documentation。

    subprocess.call。这基本上就像Popen 类并接受所有相同的参数,但它只是等待命令完成并为您提供返回码。例如:

    return_code = subprocess.call("echo Hello World", shell=True)
    

    subprocess.run。仅限 Python 3.5+。与上述类似,但更加灵活,在命令执行完成时返回CompletedProcess 对象。

    os.forkos.execos.spawn 与它们的 C 语言对应物相似,但我不建议直接使用它们。

subprocess 模块应该是您使用的。

最后,请注意,对于将最终命令作为字符串传递给 shell 执行的所有方法,您有责任将其转义。 如果您传递的字符串的任何部分不能被完全信任,则会有严重的安全隐患。例如,如果用户正在输入字符串的某些/任何部分。如果您不确定,请仅将这些方法与常量一起使用。为了给您暗示暗示,请考虑以下代码:

print subprocess.Popen("echo %s " % user_input, stdout=PIPE).stdout.read()

并想象用户输入了“my mama didnt love me &amp;&amp; rm -rf /”,这可能会擦除整个文件系统。

【讨论】:

很好的答案/解释。这个答案如何证明本文所述的 Python 座右铭? fastcompany.com/3026446/… “从风格上讲,Perl 和 Python 有不同的哲学。Perl 最著名的座右铭是“有不止一种方法可以做到”。Python 被设计为有一种明显的方法来做到这一点“看起来应该是另一种方法!在 Perl 中,我只知道执行命令的两种方法 - 使用反引号或 open 如果使用 Python 3.5+,请使用 subprocess.run()。 docs.python.org/3.5/library/subprocess.html#subprocess.run 人们通常需要知道的是子进程的 STDOUT 和 STDERR 做了什么,因为如果它们被忽略,在某些(相当常见的)条件下,最终子进程将发出系统调用写入STDOUT(也有STDERR?),这将超过操作系统为进程提供的输出缓冲区,并且操作系统将导致它阻塞,直到某个进程从该缓冲区读取。那么,按照目前推荐的方式,subprocess.run(..)"This does not capture stdout or stderr by default." 究竟意味着什么? subprocess.check_output(..) 和 STDERR 呢? 您推荐的哪些命令会阻止我的脚本?即,如果我想在 for 循环中运行多个命令,我该怎么做而不阻塞我的 python 脚本?我不关心命令的输出,我只想运行很多命令。 这可以说是错误的方式。大多数人只需要subprocess.run() 或其兄弟姐妹subprocess.check_call() 等。对于这些还不够的情况,请参阅subprocess.Popen()os.popen() 可能根本不应该被提及,或者甚至在“破解你自己的 fork/exec/spawn 代码”之后出现。【参考方案3】:

您可以使用 subprocess 模块中的 Popen 运行任何命令。

from subprocess import Popen

首先,使用您要运行的所有参数创建命令对象。例如,在下面的 sn-p 中,gunicorm 命令对象已由所有参数组成:

cmd = (
        "gunicorn "
        "-c gunicorn_conf.py "
        "-w workers "
        "--timeout timeout "
        "-b address:port "
        "--limit-request-line 0 "
        "--limit-request-field_size 0 "
        "--log-level debug "
        "--max-requests max_requests "
        "manage:app").format(**locals())

然后这个命令对象与Popen一起使用来实例化一个进程:

process = Popen(cmd, shell=True)

这个过程也可以根据任何信号终止,使用下面的代码行:

Popen.terminate(process)

你可以等到上面的命令执行完成:

process.wait()

【讨论】:

【参考方案4】:

典型实现:

import subprocess

p = subprocess.Popen('ls', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
for line in p.stdout.readlines():
    print line,
retval = p.wait()

您可以随意使用管道中的stdout 数据做您想做的事情。实际上,您可以简单地省略这些参数(stdout=stderr=),它的行为就像 os.system()

【讨论】:

.readlines() 一次读取 所有 行,即它会阻塞直到子进程退出(关闭其管道末端)。要实时阅读(如果没有缓冲问题),您可以:for line in iter(p.stdout.readline, ''): print line, 您能否详细说明“如果没有缓冲问题”是什么意思?如果进程确实阻塞,则子进程调用也会阻塞。我原来的例子也可能发生同样的情况。在缓冲方面还会发生什么? 子进程可能在非交互模式下使用块缓冲而不是行缓冲,所以p.stdout.readline()(注意:最后没有s)在子进程之前不会看到任何数据填充其缓冲区。如果孩子没有产生太多数据,那么输出将不是实时的。请参阅Q: Why not just use a pipe (popen())? 中的第二个原因。提供了一些解决方法in this answer (pexpect, pty, stdbuf) 缓冲问题仅在您希望实时输出时才重要,并且不适用于在收到 所有 数据之前不打印任何内容的代码 这个答案在当时还不错,但我们不应该再推荐Popen 用于简单的任务。这也不必要地指定了shell=True。尝试subprocess.run() 答案之一。【参考方案5】:

如何从 Python 执行程序或调用系统命令

简单,使用subprocess.run,它返回一个CompletedProcess对象:

>>> from subprocess import run
>>> from shlex import split
>>> completed_process = run(split('python --version'))
Python 3.8.8
>>> completed_process
CompletedProcess(args=['python', '--version'], returncode=0)

(run 想要一个词法分析的 shell 参数列表 - 这是你在 shell 中键入的内容,用空格分隔,但不是空格被引用的地方,所以使用专门的函数 split 来拆分你在 shell 中输入的内容)

为什么?

从 Python 3.5 开始,文档推荐subprocess.run:

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

这是一个最简单的用法示例 - 它完全按照要求进行:

>>> from subprocess import run
>>> from shlex import split
>>> completed_process = run(split('python --version'))
Python 3.8.8
>>> completed_process
CompletedProcess(args=['python', '--version'], returncode=0)

run 等待命令成功完成,然后返回一个CompletedProcess 对象。它可能会引发TimeoutExpired(如果你给它一个timeout=参数)或CalledProcessError(如果它失败并且你通过check=True)。

您可能从上面的示例中推断出,默认情况下,stdout 和 stderr 都会通过管道传输到您自己的 stdout 和 stderr。

我们可以检查返回的对象并查看给出的命令和返回码:

>>> completed_process.args
['python', '--version']
>>> completed_process.returncode
0

捕获输出

如果要捕获输出,可以将subprocess.PIPE 传递给相应的stderrstdout

>>> from subprocess import PIPE
>>> completed_process = run(shlex.split('python --version'), stdout=PIPE, stderr=PIPE)
>>> completed_process.stdout
b'Python 3.8.8\n'
>>> completed_process.stderr
b''

并且那些各自的属性返回字节。

传递一个命令列表

人们可能很容易从手动提供命令字符串(如问题所暗示的那样)转向提供以编程方式构建的字符串。 不要以编程方式构建字符串。这是一个潜在的安全问题。最好假设您不信任输入。

>>> import textwrap
>>> args = ['python', textwrap.__file__]
>>> cp = run(args, stdout=subprocess.PIPE)
>>> cp.stdout
b'Hello there.\n  This is indented.\n'

注意,只有args 应该按位置传递。

完整签名

这是源代码中的实际签名,如help(run)所示:

def run(*popenargs, input=None, timeout=None, check=False, **kwargs):

popenargskwargs 被赋予 Popen 构造函数。 input 可以是字节字符串(或 unicode,如果指定编码或 universal_newlines=True),将通过管道传输到子进程的标准输入。

文档对timeout=check=True 的描述比我想象的要好:

超时参数被传递给 Popen.communicate()。如果超时 过期,子进程将被杀死并等待。这 TimeoutExpired 异常将在子进程完成后重新引发 终止。

如果检查为真,并且进程以非零退出代码退出,则 将引发 CalledProcessError 异常。那的属性 异常保存参数、退出代码以及 stdout 和 stderr if 他们被俘虏了。

check=True 的这个例子比我想出的要好:

>>> subprocess.run("exit 1", shell=True, check=True)
Traceback (most recent call last):
  ...
subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1

扩展签名

这是文档中给出的扩展签名:

subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, 
shell=False, cwd=None, timeout=None, check=False, encoding=None, 
errors=None)

请注意,这表明只有 args 列表应按位置传递。所以将剩余的参数作为关键字参数传递。

打开

何时改用Popen?我很难仅根据论点找到用例。但是,直接使用 Popen 可以让您访问其方法,包括 poll、'send_signal'、'terminate' 和 'wait'。

这是the source 中给出的Popen 签名。我认为这是对信息最精确的封装(相对于help(Popen)):


def __init__(self, args, bufsize=-1, executable=None,
             stdin=None, stdout=None, stderr=None,
             preexec_fn=None, close_fds=True,
             shell=False, cwd=None, env=None, universal_newlines=None,
             startupinfo=None, creationflags=0,
             restore_signals=True, start_new_session=False,
             pass_fds=(), *, user=None, group=None, extra_groups=None,
             encoding=None, errors=None, text=None, umask=-1, pipesize=-1):

但信息量更大的是the Popen documentation:

subprocess.Popen(args, bufsize=-1, executable=None, stdin=None, stdout=None, 
stderr=None, preexec_fn=None, close_fds=True, shell=False, cwd=None,
env=None, universal_newlines=None, startupinfo=None, creationflags=0, 
restore_signals=True, start_new_session=False, pass_fds=(), *, group=None, 
extra_groups=None, user=None, umask=-1, encoding=None, errors=None, 
text=None)

在新进程中执行子程序。在 POSIX 上,该类使用 os.execvp()-like 行为来执行子程序。在 Windows 上, 该类使用 Windows CreateProcess() 函数。论据 Popen如下。

了解Popen 上的其余文档将留给读者作为练习。

【讨论】:

主进程和子进程之间双向通信的简单示例可以在这里找到:***.com/a/52841475/1349673【参考方案6】:

使用标准库中的subprocess 模块:

import subprocess
subprocess.run(["ls", "-l"])

subprocess.run 相对于os.system 的优势在于它更灵活(您可以获得stdoutstderr、"real" status code、更好的error handling 等...)。

即使the documentation for os.system 也建议使用subprocess 代替:

subprocess 模块为生成新进程和检索其结果提供了更强大的工具;使用该模块优于使用此功能。请参阅 subprocess 文档中的 Replacing Older Functions with the subprocess Module 部分,了解一些有用的食谱。

在 Python 3.4 及更早版本上,使用 subprocess.call 而不是 .run

subprocess.call(["ls", "-l"])

【讨论】:

有没有办法使用变量替换? IE 我尝试使用call(["echo", "$PATH"]) 来执行echo $PATH,但它只是回显了文字字符串$PATH,而不是进行任何替换。我知道我可以获得 PATH 环境变量,但我想知道是否有一种简单的方法可以让命令的行为与我在 bash 中执行它时完全一样。 @KevinWheeler 你必须使用shell=True 才能工作。 @KevinWheeler 你不应该使用shell=True,为此Python自带了os.path.expandvars。在您的情况下,您可以写:os.path.expandvars("$PATH")。 @SethMMorton 请重新考虑您的评论-> Why not to use shell=True 如果我想通过管道传输东西怎么办? pip list | grep anatome? 我不明白,import os; os.system('pip list | grep anatome') 有什么问题?至少这允许您像我的示例所示那样进行管道处理。目前尚不清楚如何使用import subprocess; subprocess.run(["ls", "-l"]) 来做到这一点。【参考方案7】:

从 2018 年 6 月 27 日发布的 Python 3.7.0 (https://docs.python.org/3/whatsnew/3.7.html)开始,您可以以最强大但同样简单的方式实现您想要的结果。该答案旨在以简短的方式向您展示各种选项的基本摘要。如需深入解答,请参阅其他答案。


TL;2021 年 DR

os.system(...) 的最大优势在于其简单性。 subprocess 更好并且仍然易于使用,尤其是从 Python 3.5 开始。

import subprocess
subprocess.run("ls -a", shell=True)

注意:这是您问题的确切答案 - 运行命令

像在贝壳里


首选方式

如果可能,移除 shell 开销并直接运行命令(需要列表)。

import subprocess
subprocess.run(["help"])
subprocess.run(["ls", "-a"])

在列表中传递程序参数。 不要将\"-escaping 用于包含空格的参数。


高级用例

检查输出

以下代码不言自明:

import subprocess
result = subprocess.run(["ls", "-a"], capture_output=True, text=True)
if "***-logo.png" in result.stdout:
    print("You're a fan!")
else:
    print("You're not a fan?")

result.stdout 是所有正常的程序输出不包括错误。阅读result.stderr 获取它们。

capture_output=True - 打开捕获。否则result.stderrresult.stdout 将是None。可从 Python 3.7 获得。

text=True - Python 3.7 中添加的便利参数,可将接收到的二进制数据转换为您可以轻松使用的 Python 字符串。

检查返回码

if result.returncode == 127: print("The program failed for some weird reason")
elif result.returncode == 0: print("The program succeeded")
else: print("The program failed unexpectedly")

如果只想检查程序是否成功(returncode == 0),否则抛出异常,有一个更方便的函数:

result.check_returncode()

但它是 Python,所以有一个更方便的参数 check 会自动为你做同样的事情:

result = subprocess.run(..., check=True)

stderr 应该在标准输出里面

您可能希望在标准输出中包含所有程序输出,甚至是错误。为此,请运行

result = subprocess.run(..., stderr=subprocess.STDOUT)

result.stderr 将成为 Noneresult.stdout 将包含所有内容。

使用带有参数字符串的 shell=False

shell=False 需要一个 list 参数。但是,您可以使用 shlex 自行拆分参数字符串。

import subprocess
import shlex
subprocess.run(shlex.split("ls -a"))

就是这样。

常见问题

当您遇到这个问题时,很有可能您刚开始使用 Python。让我们来看看一些常见的问题。

FileNotFoundError: [Errno 2] 没有这样的文件或目录:'ls -a': 'ls -a'

您正在运行一个没有 shell=True 的子进程。使用列表 (["ls", "-a"]) 或设置 shell=True

TypeError: [...] NoneType [...]

检查您是否设置了capture_output=True

TypeError:需要一个类似字节的对象,而不是 [...]

您总是从您的程序中收到字节结果。如果您想像普通字符串一样使用它,请设置text=True

subprocess.CalledProcessError: Command '[...]' 返回非零退出状态 1。

您的命令没有成功运行。您可以禁用返回码检查或检查您的实际程序的有效性。

TypeError: init() 得到了一个意外的关键字参数 [...]

您可能使用的 Python 版本可能早于 3.7.0;将其更新为可用的最新版本。否则,此 Stack Overflow 帖子中还有其他答案,向您展示了较旧的替代解决方案。

【讨论】:

“os.system(...) 的最大优势在于它的简单性。子进程更好” - 子进程如何更好?我很高兴使用 os.system,不知道切换到子进程和记住额外的 shell=True 对我有什么好处。子流程中什么样的东西比较好? 您说得对,就简单的“盲”执行而言,os.system(...) 是执行命令的合理选择。然而,用例是相当有限的——一旦你想捕获输出,你必须使用一个完整的其他库,然后你就开始在代码中同时使用子进程和操作系统来处理类似的用例。我更喜欢保持代码干净并只使用其中一个。其次,我会将该部分放在顶部,但 TL;DR 必须确切地回答这个问题,您应该使用shell=True,而是使用我的'已经写在Preferred Way 部分。 os.system(...)shell=True 的问题是你正在生成一个新的shell 进程,只是为了执行你的命令。这意味着,您必须进行手动转义,这并不像您想象的那么简单——尤其是在同时针对 POSIX 和 Windows 时。对于用户提供的输入,这是不行的(想象一下用户用" 引号输入了一些东西——你也必须转义它们)。此外,shell 进程本身可能会加载您不需要的代码 - 它不仅会延迟程序,而且还可能导致意外的副作用,以错误的返回码结束。 总结一下,os.system(...) 确实可以使用。但是,一旦您编写的不仅仅是一个快速的 python 帮助脚本,我建议您在没有shell=True 的情况下使用 subprocess.run。有关 os.system 缺点的更多信息,我想建议您阅读这个 SO 答案:***.com/a/44731082/6685358 谢谢!我想编辑“更好”以包含该链接,但我收到有关完整编辑队列的错误。【参考方案8】:

我将它用于 Python 3.6+:

import subprocess
def execute(cmd):
    """
        Purpose  : To execute a command and return exit status
        Argument : cmd - command to execute
        Return   : result, exit_code
    """
    process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    (result, error) = process.communicate()
    rc = process.wait()
    if rc != 0:
        print ("Error: failed to execute command: ", cmd)
        print (error.rstrip().decode("utf-8"))
    return result.rstrip().decode("utf-8"), serror.rstrip().decode("utf-8")
# def

【讨论】:

不要使用 set shell=True 运行命令,它会打开程序命令注入漏洞。您应该将命令作为带有参数cmd=["/bin/echo", "hello word"] 的列表传递。 docs.python.org/3/library/…【参考方案9】:

有多种方法可以从 Python 中调用外部命令。有一些功能和模块具有很好的辅助功能,可以使它变得非常容易。但最推荐的是subprocess 模块。

import subprocess as s
s.call(["command.exe", "..."])

调用函数将启动外部进程,传递一些命令行参数并等待它完成。完成后,您继续执行。 call 函数中的参数通过列表传递。列表中的第一个参数通常是可执行文件形式的命令,列表中的后续参数是您想要传递的任何内容。

如果您之前在 Windows 中从命令行调用过进程,您会意识到您经常需要引用参数。你需要在它周围加上引号。如果有空格,则有一个反斜杠,并且有一些复杂的规则,但是您可以通过使用 subprocess 模块在 Python 中避免很多这样的情况,因为它是一个列表,并且每个项目都是不同的,python 可以得到为您正确引用。

最后,在列表之后,有许多可选参数,其中一个是 shell,如果你将 shell 设置为 true,那么你的命令将像在命令提示符下键入一样运行.

s.call(["command.exe", "..."], shell=True)

这使您可以访问管道等功能,可以重定向到文件,可以在一件事中调用多个命令。

还有一件事,如果您的脚本依赖于成功的过程,那么您想要检查结果,并且可以使用 check call helper 函数检查结果。

s.check_call(...)

它与调用函数完全相同,它采用相同的参数,采用相同的列表,您可以传入任何额外的参数,但它会等待函数完成。如果函数的退出代码不是零,它将通过 python 脚本中的异常。

最后,如果您想要更严格地控​​制Popen 构造函数,它也来自subprocess 模块。它也采用与 incall 和 check_call 函数相同的参数,但它返回一个表示正在运行的进程的对象。

p=s.Popen("...")

它不会等待正在运行的进程完成,也不会立即抛出任何异常,但它会为您提供一个对象,让您可以执行诸如等待它完成之类的操作,让您与之通信,您可以重定向标准输入,标准输出,如果你想在其他地方显示输出等等。

【讨论】:

【参考方案10】:

Sultan 是用于此目的的最新包。它提供了一些关于管理用户权限和添加有用的错误消息的细节。

from sultan.api import Sultan

with Sultan.load(sudo=True, hostname="myserver.com") as sultan:
  sultan.yum("install -y tree").run()

【讨论】:

【参考方案11】:

对于在 Python 3.5+ 中使用 subprocess,以下在 Linux 上对我有用:

import subprocess

# subprocess.run() returns a completed process object that can be inspected
c = subprocess.run(["ls", "-ltrh"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print(c.stdout.decode('utf-8'))

正如the documentation 中提到的,PIPE 值是字节序列,为了正确显示它们应该考虑解码。对于更高版本的 Python,text=Trueencoding='utf-8' 被添加到 subprocess.run() 的 kwargs 中。

上述代码的输出为:

total 113M
-rwxr-xr-x  1 farzad farzad  307 Jan 15  2018 ***script
-rwxrwxr-x  1 farzad farzad  204 Jan 15  2018 ex
drwxrwxr-x  4 farzad farzad 4.0K Jan 22  2018 scripts
.... # Some other lines

【讨论】:

【参考方案12】:

我写了一个小库来帮助解决这个用例:

https://pypi.org/project/citizenshell/

可以使用安装

pip install citizenshell

然后使用如下:

from citizenshell import sh
assert sh("echo Hello World") == "Hello World"

您可以将标准输出与标准错误分开并提取退出代码,如下所示:

result = sh(">&2 echo error && echo output && exit 13")
assert result.stdout() == ["output"]
assert result.stderr() == ["error"]
assert result.exit_code() == 13

而且很酷的是,您不必等待底层 shell 退出就可以开始处理输出:

for line in sh("for i in 1 2 3 4; do echo -n 'It is '; date +%H:%M:%S; sleep 1; done", wait=False)
    print ">>>", line + "!"

将打印可用的行,这要归功于 wait=False

>>> It is 14:24:52!
>>> It is 14:24:53!
>>> It is 14:24:54!
>>> It is 14:24:55!

更多示例请访问https://github.com/meuter/citizenshell

【讨论】:

【参考方案13】:

Invoke 是一个 Python(2.7 和 3.4+)任务执行工具和库。它为运行 shell 命令提供了一个干净的高级 API:

>>> from invoke import run
>>> cmd = "pip install -r requirements.txt"
>>> result = run(cmd, hide=True, warn=True)
>>> print(result.ok)
True
>>> print(result.stdout.splitlines()[-1])
Successfully installed invocations-0.13.0 pep8-1.5.7 spec-1.3.1

【讨论】:

这是一个很棒的图书馆。前几天我试图向同事解释它并这样描述它:invokesubprocess 就像 requestsurllib3【参考方案14】:

在 Linux 下,如果您想调用将独立执行的外部命令(将在 Python 脚本终止后继续运行),您可以使用简单的队列作为 task spooler 或 at 命令。

任务假脱机示例:

import os
os.system('ts <your-command>')

关于任务后台处理程序 (ts) 的说明:

    您可以设置要运行的并发进程数(“槽”):

    ts -S &lt;number-of-slots&gt;

    安装ts 不需要管理员权限。您可以使用简单的make 从源代码下载并编译它,将其添加到您的路径中即可。

【讨论】:

ts 在我所知道的任何发行版中都不是标准的,尽管指向at 的指针有点用处。您可能还应该提到batch。与其他地方一样,os.system() 建议至少应该提到subprocess 是它的推荐替代品。【参考方案15】:

有许多不同的库允许您使用 Python 调用外部命令。对于每个库,我都给出了描述并展示了调用外部命令的示例。我用作示例的命令是ls -l(列出所有文件)。如果您想了解有关我列出的任何库的更多信息,并链接了每个库的文档。

来源

子进程:https://docs.python.org/3.5/library/subprocess.html shlex:https://docs.python.org/3/library/shlex.html 操作系统:https://docs.python.org/3.5/library/os.html sh:https://amoffat.github.io/sh/ 铅垂:https://plumbum.readthedocs.io/en/latest/ 期待:https://pexpect.readthedocs.io/en/stable/ 面料:http://www.fabfile.org/ 特使:https://github.com/kennethreitz/envoy 命令:https://docs.python.org/2/library/commands.html

这些都是库

希望这将帮助您决定使用哪个库:)

子进程

子进程允许您调用外部命令并将它们连接到它们的输入/输出/错误管道(stdin、stdout 和 stderr)。子进程是运行命令的默认选择,但有时其他模块更好。

subprocess.run(["ls", "-l"]) # Run command
subprocess.run(["ls", "-l"], stdout=subprocess.PIPE) # This will run the command and return any output
subprocess.run(shlex.split("ls -l")) # You can also use the shlex library to split the command

操作系统

os 用于“操作系统相关功能”。也可以用os.systemos.popen调用外部命令(注意:还有一个subprocess.popen)。 os 将始终运行 shell,对于不需要或不知道如何使用 subprocess.run 的人来说是一个简单的替代方案。

os.system("ls -l") # Run command
os.popen("ls -l").read() # This will run the command and return any output

sh 是一个子进程接口,可让您像调用函数一样调用程序。如果您想多次运行一个命令,这很有用。

sh.ls("-l") # Run command normally
ls_cmd = sh.Command("ls") # Save command as a variable
ls_cmd() # Run command as if it were a function

铅锤

plumbum 是一个用于“类似脚本”的 Python 程序的库。你可以像sh那样调用函数之类的程序。如果您想在没有外壳的情况下运行管道,Plumbum 很有用。

ls_cmd = plumbum.local("ls -l") # Get command
ls_cmd() # Run command

期待

pexpect 可让您生成子应用程序、控制它们并在其输出中查找模式。对于需要 Unix 上的 tty 的命令,这是子进程的更好替代方案。

pexpect.run("ls -l") # Run command as normal
child = pexpect.spawn('scp foo user@example.com:.') # Spawns child application
child.expect('Password:') # When this is the output
child.sendline('mypassword')

面料

fabric 是 Python 2.5 和 2.7 库。它允许您执行本地和远程 shell 命令。 Fabric 是在安全 shell (SSH) 中运行命令的简单替代方案

fabric.operations.local('ls -l') # Run command as normal
fabric.operations.local('ls -l', capture = True) # Run command and receive output

特使

envoy 被称为“人类的子进程”。它用作subprocess 模块的便捷包装器。

r = envoy.run("ls -l") # Run command
r.std_out # Get output

命令

commands 包含 os.popen 的包装函数,但它已从 Python 3 中删除,因为 subprocess 是更好的选择。

【讨论】:

【参考方案16】:

我会推荐以下方法'run',它将帮助我们获得standard output、standard error和作为字典的退出状态; this的调用者可以通过'run'方法读取字典返回来了解进程的实际状态。

  def run (cmd):
       print "+ DEBUG exec(0)".format(cmd)
       p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=True)
       (out, err) = p.communicate()
       ret        = p.wait()
       out        = filter(None, out.split('\n'))
       err        = filter(None, err.split('\n'))
       ret        = True if ret == 0 else False
       return dict('output': out, 'error': err, 'status': ret)
  #end

【讨论】:

这没有完全重新实现subprocess.run() 之类的东西。当不是绝对必要时,你应该特别避免shell=True #end 是干什么用的?【参考方案17】:

我为此编写了一个库,shell.py

它现在基本上是 popen 和 shlex 的包装器。它还支持管道命令,因此您可以在 Python 中更轻松地链接命令。因此,您可以执行以下操作:

ex('echo hello shell.py') | "awk 'print $2'"

【讨论】:

【参考方案18】:

经过一些研究,我有以下代码对我来说非常有效。它基本上是实时打印标准输出和标准错误。

stdout_result = 1
stderr_result = 1


def stdout_thread(pipe):
    global stdout_result
    while True:
        out = pipe.stdout.read(1)
        stdout_result = pipe.poll()
        if out == '' and stdout_result is not None:
            break

        if out != '':
            sys.stdout.write(out)
            sys.stdout.flush()


def stderr_thread(pipe):
    global stderr_result
    while True:
        err = pipe.stderr.read(1)
        stderr_result = pipe.poll()
        if err == '' and stderr_result is not None:
            break

        if err != '':
            sys.stdout.write(err)
            sys.stdout.flush()


def exec_command(command, cwd=None):
    if cwd is not None:
        print '[' + ' '.join(command) + '] in ' + cwd
    else:
        print '[' + ' '.join(command) + ']'

    p = subprocess.Popen(
        command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd
    )

    out_thread = threading.Thread(name='stdout_thread', target=stdout_thread, args=(p,))
    err_thread = threading.Thread(name='stderr_thread', target=stderr_thread, args=(p,))

    err_thread.start()
    out_thread.start()

    out_thread.join()
    err_thread.join()

    return stdout_result + stderr_result

【讨论】:

您的代码可能会在子进程退出而缓冲一些数据时丢失数据。改为阅读直到 EOF,请参阅 teed_call()【参考方案19】:

在 Python 中运行外部命令有很多不同的方法, 他们都有自己的优点和缺点。

我和我的同事一直在编写 Python 系统管理工具,所以我们需要运行很多外部命令,有时你希望它们阻塞或异步运行、超时、每秒更新等。

也有不同的方式来处理返回码和错误, 并且您可能想要解析输出,并提供新的输入(以expect 风格)。或者您需要重定向 standard input、standard output 和 standard error 以在不同的 tty 中运行(例如,使用 GNU Screen 时)。

因此,您可能必须围绕外部命令编写大量包装器。所以这是我们编写的一个 Python 模块,它可以处理 几乎任何您想要的东西,如果没有,它非常灵活,因此您可以轻松扩展它:

https://github.com/hpcugent/vsc-base/blob/master/lib/vsc/utils/run.py

它不能独立工作,需要我们的一些其他工具,并且多年来获得了许多专门的功能,因此它可能不是您的直接替代品,但它可以为您提供很多有关运行命令的 Python 内部如何工作以及如何处理某些情况的想法的信息。

【讨论】:

【参考方案20】:

大多数情况:

在大多数情况下,您只需要像这样的简短 sn-p 代码

import subprocess
import shlex

source = "test.txt"
destination = "test_copy.txt"

base = "cp source destination'"
cmd = base.format(source=source, destination=destination)
subprocess.check_call(shlex.split(cmd))

简洁明了

subprocess.check_call 运行带参数的命令并等待 命令完成。

shlex.split 使用类似 shell 的语法拆分字符串 cmd

其他情况:

如果这对某些特定命令不起作用,很可能您遇到了 command-line interpreters 的问题。操作系统选择了默认的,它不适合您的程序类型或在系统可执行路径上找不到合适的。

示例:

在 Unix 系统上使用重定向操作符

input_1 = "input_1.txt"
input_2 = "input_2.txt"
output = "merged.txt"
base_command = "/bin/bash -c 'cat input >> output'"

base_command.format(input_1, output=output)
subprocess.check_call(shlex.split(base_command))

base_command.format(input_2, output=output)
subprocess.check_call(shlex.split(base_command))

正如The Zen of Python 所述:显式优于 隐式

所以如果使用 Python >=3.6 函数,它看起来像这样:

import subprocess
import shlex

def run_command(cmd_interpreter: str, command: str) -> None:
    base_command = f"cmd_interpreter -c 'command'"
    subprocess.check_call(shlex.split(base_command)

【讨论】:

【参考方案21】:

如果您正在编写 Python shell 脚本并在系统上安装了 IPython,则可以使用 bang 前缀在 IPython 中运行 shell 命令:

!ls
filelist = !ls

【讨论】:

@PeterMortensen 我不认为它在 DOS 中工作,但它应该在 Cygwin 中工作。【参考方案22】:

我建议使用 subprocess 模块而不是 os.system,因为它会为您进行 shell 转义,因此更安全。

subprocess.call(['ping', 'localhost'])

【讨论】:

如果你想用带参数的命令创建一个列表,当shell=False时可以和subprocess一起使用的列表,然后使用shlex.split一个简单的方法来做到这一点docs.python.org/2/library/shlex.html#shlex.split(根据文档docs.python.org/2/library/subprocess.html#popen-constructor,这是推荐的方法) 这是不正确的:“它会为你进行 shell 转义,因此更安全”。 subprocess 不进行 shell 转义, subprocess 不会通过 shell 传递您的命令,因此无需 shell 转义。【参考方案23】:

os.popen() 是执行命令最简单、最安全的方式。您可以执行在命令行上运行的任何命令。此外,您还可以使用os.popen().read() 捕获命令的输出

你可以这样做:

import os
output = os.popen('Your Command Here').read()
print (output)

列出当前目录中所有文件的示例:

import os
output = os.popen('ls').read()
print (output)
# Outputs list of files in the directory

【讨论】:

【参考方案24】:

如果您在命令中使用用户输入,则可以使用:

from os import getcwd
from subprocess import check_output
from shlex import quote

def sh(command):
    return check_output(quote(command), shell=True, cwd=getcwd(), universal_newlines=True).strip()

并将其用作

branch = sh('git rev-parse --abbrev-ref HEAD')

shell=True 将生成一个外壳,因此您可以使用管道和类似外壳的东西sh('ps aux | grep python')。这对于运行硬编码命令和处理其输出非常方便。 universal_lines=True 确保输出以字符串而不是二进制形式返回。

cwd=getcwd() 将确保命令在与解释器相同的工作目录下运行。这很方便 Git 命令像上面的 Git 分支名称示例一样工作。

一些食谱

可用内存(以 MB 为单位):sh('free -m').split('\n')[1].split()[1] 可用空间/百分比sh('df -m /').split('\n')[1].split()[4][0:-1] CPU 负载sum(map(float, sh('ps -ef -o pcpu').split('\n')[1:])

但这对于用户输入是不安全的,来自文档:

安全注意事项

与其他一些 popen 函数不同,这个实现永远不会 隐式调用系统外壳。这意味着所有字符, 包括 shell 元字符,可以安全地传递给孩子 过程。如果通过 shell=True 显式调用 shell,它是 应用程序有责任确保所有空格和 元字符被适当引用以避免 shell 注入 漏洞。

当使用 shell=True 时,可以使用 shlex.quote() 函数 正确转义字符串中的空格和 shell 元字符 将用于构造 shell 命令。

即使使用shlex.quote(),在 shell 命令上使用用户输入时,最好保持一点偏执。一种选择是使用硬编码命令来获取一些通用输出并通过用户输入进行过滤。无论如何,使用shell=False 将确保只执行您要执行的确切进程,否则您会收到No such file or directory 错误。

shell=True 也有一些性能影响,从我的测试来看,它似乎比shell=False(默认值)慢了大约 20%。

In [50]: timeit("check_output('ls -l'.split(), universal_newlines=True)", number=1000, globals=globals())
Out[50]: 2.6801227919995654

In [51]: timeit("check_output('ls -l', universal_newlines=True, shell=True)", number=1000, globals=globals())
Out[51]: 3.243950183999914

【讨论】:

【参考方案25】:

从OpenStackNeutron获取网络ID:

#!/usr/bin/python
import os
netid = "nova net-list | awk '/ External /  print $2 '"
temp = os.popen(netid).read()  /* Here temp also contains new line (\n) */
networkId = temp.rstrip()
print(networkId)

nova 网表

的输出
+--------------------------------------+------------+------+
| ID                                   | Label      | CIDR |
+--------------------------------------+------------+------+
| 431c9014-5b5d-4b51-a357-66020ffbb123 | test1      | None |
| 27a74fcd-37c0-4789-9414-9531b7e3f126 | External   | None |
| 5a2712e9-70dc-4b0e-9281-17e02f4684c9 | management | None |
| 7aa697f5-0e60-4c15-b4cc-9cb659698512 | Internal   | None |
+--------------------------------------+------------+------+

print(networkId)

的输出
27a74fcd-37c0-4789-9414-9531b7e3f126

【讨论】:

你不应该在 2016 年推荐 os.popen()。Awk 脚本可以很容易地替换为原生 Python 代码。【参考方案26】:

一个简单的方法是使用os module:

import os
os.system('ls')

或者,您也可以使用子流程模块:

import subprocess
subprocess.check_call('ls')

如果您希望将结果存储在变量中,请尝试:

import subprocess
r = subprocess.check_output('ls')

【讨论】:

【参考方案27】:

使用subprocess.call:

from subprocess import call

# Using list
call(["echo", "Hello", "world"])

# Single string argument varies across platforms so better split it
call("echo Hello world".split(" "))

【讨论】:

【参考方案28】:

使用标准库

使用subprocess module(Python 3):

import subprocess
subprocess.run(['ls', '-l'])

这是推荐的标准方式。然而,更复杂的任务(管道、输出、输入等)的构建和编写可能会很乏味。

Python 版本注意事项:如果您仍在使用 Python 2,subprocess.call 的工作方式类似。

专业提示:shlex.split 可以帮助您解析runcall 和其他subprocess 函数的命令,以防您不希望(或者您不能!)提供它们以列表的形式:

import shlex
import subprocess
subprocess.run(shlex.split('ls -l'))

有外部依赖

如果您不介意外部依赖,请使用plumbum:

from plumbum.cmd import ifconfig
print(ifconfig['wlan0']())

这是最好的subprocess 包装器。它是跨平台的,即它适用于 Windows 和类 Unix 系统。通过pip install plumbum安装。

另一个流行的库是sh:

from sh import ifconfig
print(ifconfig('wlan0'))

然而,sh 放弃了对 Windows 的支持,所以它不像以前那么棒了。通过pip install sh安装。

【讨论】:

【参考方案29】:

更新:

subprocess.run 是推荐的方法as of Python 3.5 如果您的代码不需要保持与早期 Python 版本的兼容性。它更加一致,并提供与 Envoy 类似的易用性。 (不过,管道并不那么简单。请参阅 this question for how。)

以下是来自the documentation 的一些示例。

运行一个进程:

>>> subprocess.run(["ls", "-l"])  # Doesn't capture output
CompletedProcess(args=['ls', '-l'], returncode=0)

运行失败引发:

>>> subprocess.run("exit 1", shell=True, check=True)
Traceback (most recent call last):
  ...
subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1

捕获输出:

>>> subprocess.run(["ls", "-l", "/dev/null"], stdout=subprocess.PIPE)
CompletedProcess(args=['ls', '-l', '/dev/null'], returncode=0,
stdout=b'crw-rw-rw- 1 root root 1, 3 Jan 23 16:23 /dev/null\n')

原答案:

我建议尝试Envoy。它是子进程的包装器,它反过来 aims to replace 旧模块和函数。 Envoy 是人类的子进程。

来自the README的示例用法:

>>> r = envoy.run('git config', data='data to pipe in', timeout=2)

>>> r.status_code
129
>>> r.std_out
'usage: git config [options]'
>>> r.std_err
''

也管东西:

>>> r = envoy.run('uptime | pbcopy')

>>> r.command
'pbcopy'
>>> r.status_code
0

>>> r.history
[<Response 'uptime'>]

【讨论】:

【参考方案30】:

os.system 不允许您存储结果,因此如果您想将结果存储在某个列表或其他内容中,subprocess.call 可以。

【讨论】:

以上是关于如何执行程序或调用系统命令?的主要内容,如果未能解决你的问题,请参考以下文章

怎样在一个 C 程序中调用另一个程序 (独立可执行的程序, 或系统命令)?

C或C++如何通过程序执行shell命令并获取命令执行结果?

如何在windows bat脚本中调用Cygwin并执行命令

LINUX对shell命令的拦截

如何在nodejs里调用执行系统命令

命令执行漏洞