69

如何使用 Pythonsubprocess模块执行以下 shell 命令?

echo "input data" | awk -f script.awk | sort > outfile.txt

输入数据将来自一个字符串,所以我实际上并不需要echo. 我已经走了这么远,谁能解释我如何让它通过管道sort

p_awk = subprocess.Popen(["awk","-f","script.awk"],
                          stdin=subprocess.PIPE,
                          stdout=file("outfile.txt", "w"))
p_awk.communicate( "input data" )

更新:请注意,虽然下面接受的答案实际上并未按要求回答问题,但我相信 S.Lott 是正确的,最好避免首先解决该问题!

4

9 回答 9

51

您会对以下内容更满意。

import subprocess

awk_sort = subprocess.Popen( "awk -f script.awk | sort > outfile.txt",
    stdin=subprocess.PIPE, shell=True )
awk_sort.communicate( b"input data\n" )

将部分工作委托给 shell。让它通过管道连接两个进程。

你会更高兴将“script.awk”重写为 Python,消除 awk 和管道。

编辑. 建议 awk 没有帮助的一些原因。

[有太多理由通过评论回复。]

  1. awk 正在添加一个没有重要价值的步骤。awk 的处理没有什么是 Python 无法处理的独特之处。

  2. 对于大型数据集,从 awk 到排序的流水线可能会缩短处理时间。对于短数据集,它没有显着的好处。快速测量awk >file ; sort fileawk | sort揭示并发性会有所帮助。使用排序,它很少有帮助,因为排序不是一次性过滤器。

  3. “Python to sort”处理(而不是“Python to awk to sort”)的简单性可以防止在这里提出确切的问题。

  4. Python - 虽然比 awk 更冗长 - 也是明确的,其中 awk 具有某些对新手不透明的隐含规则,并且让非专业人士感到困惑。

  5. awk(就像 shell 脚本本身)添加了另一种编程语言。如果所有这些都可以用一种语言(Python)完成,那么消除 shell 和 awk 编程就消除了两种编程语言,从而使人们可以专注于任务中产生价值的部分。

底线:awk 不能增加显着的价值。在这种情况下,awk 是净成本;它增加了足够的复杂性,因此有必要提出这个问题。删除 awk 将是一个净收益。

侧边栏为什么构建管道 ( a | b) 如此困难。

当外壳遇到它时,a | b它必须执行以下操作。

  1. fork 原始 shell 的子进程。这最终会变成b。

  2. 构建一个 os 管道。(不是 Python subprocess.PIPE),而是os.pipe()返回两个通过公共缓冲区连接的新文件描述符的调用。此时,该进程具有来自其父级的标准输入、标准输出、标准错误,以及一个将是“a 的标准输出”和“b 的标准输入”的文件。

  3. 叉一个孩子。孩子用新的 a 的标准输出替换它的标准输出。执行a流程。

  4. b 子关闭用新 b 的标准输入替换其标准输入。执行b流程。

  5. b 孩子等待 a 完成。

  6. 父母正在等待 b 完成。

我认为上面可以递归地用于 spawn a | b | c,但是你必须隐式地用括号括起来长管道,把它们当作a | (b | c).

由于 Python 有os.pipe(),os.exec()os.fork(), 并且您可以替换sys.stdinand sys.stdout,所以有一种方法可以在纯 Python 中完成上述操作。实际上,您可以使用os.pipe()和来计算一些快捷方式subprocess.Popen

但是,将该操作委托给 shell 会更容易。

于 2008-11-17T13:32:13.787 回答
31
import subprocess

some_string = b'input_data'

sort_out = open('outfile.txt', 'wb', 0)
sort_in = subprocess.Popen('sort', stdin=subprocess.PIPE, stdout=sort_out).stdin
subprocess.Popen(['awk', '-f', 'script.awk'], stdout=sort_in, 
                 stdin=subprocess.PIPE).communicate(some_string)
于 2012-02-06T17:20:01.037 回答
20

要模拟 shell 管道:

from subprocess import check_call

check_call('echo "input data" | a | b > outfile.txt', shell=True)

不调用 shell(参见17.1.4.2. 替换 shell 管道):

#!/usr/bin/env python
from subprocess import Popen, PIPE

a = Popen(["a"], stdin=PIPE, stdout=PIPE)
with a.stdin:
    with a.stdout, open("outfile.txt", "wb") as outfile:
        b = Popen(["b"], stdin=a.stdout, stdout=outfile)
    a.stdin.write(b"input data")
statuses = [a.wait(), b.wait()] # both a.stdin/stdout are closed already

plumbum提供了一些语法糖:

#!/usr/bin/env python
from plumbum.cmd import a, b # magic

(a << "input data" | b > "outfile.txt")()

的模拟:

#!/bin/sh
echo "input data" | awk -f script.awk | sort > outfile.txt

是:

#!/usr/bin/env python
from plumbum.cmd import awk, sort

(awk["-f", "script.awk"] << "input data" | sort > "outfile.txt")()
于 2013-05-23T09:01:24.997 回答
6

公认的答案是回避这个问题。这是一个链接多个进程的输出的片段:请注意,它还打印(有点)等效的 shell 命令,因此您可以运行它并确保输出正确。

#!/usr/bin/env python3

from subprocess import Popen, PIPE

# cmd1 : dd if=/dev/zero bs=1m count=100
# cmd2 : gzip
# cmd3 : wc -c
cmd1 = ['dd', 'if=/dev/zero', 'bs=1M', 'count=100']
cmd2 = ['tee']
cmd3 = ['wc', '-c']
print(f"Shell style : {' '.join(cmd1)} | {' '.join(cmd2)} | {' '.join(cmd3)}")

p1 = Popen(cmd1, stdout=PIPE, stderr=PIPE) # stderr=PIPE optional, dd is chatty
p2 = Popen(cmd2, stdin=p1.stdout, stdout=PIPE)
p3 = Popen(cmd3, stdin=p2.stdout, stdout=PIPE)

print("Output from last process : " + (p3.communicate()[0]).decode())

# thoretically p1 and p2 may still be running, this ensures we are collecting their return codes
p1.wait()
p2.wait()
print("p1 return: ", p1.returncode)
print("p2 return: ", p2.returncode)
print("p3 return: ", p3.returncode)
于 2018-12-09T00:09:20.220 回答
2

http://www.python.org/doc/2.5.2/lib/node535.html很好地涵盖了这一点。这其中有没有你不明白的部分?

您的程序将非常相似,但是第二个程序Popen将 stdout= 指向文件,并且您不需要其.communicate().

于 2008-11-17T12:26:50.890 回答
2

受到@Cristian 回答的启发。我遇到了同样的问题,但使用了不同的命令。所以我把我测试过的例子,我相信这可能会有所帮助:

grep_proc = subprocess.Popen(["grep", "rabbitmq"],
                             stdin=subprocess.PIPE, 
                             stdout=subprocess.PIPE)
subprocess.Popen(["ps", "aux"], stdout=grep_proc.stdin)
out, err = grep_proc.communicate()

这是经过测试的。

做了什么

  • grep使用管道中的标准输入声明延迟执行。该命令将在ps命令执行时执行,此时管道将被ps.
  • 调用主命令ps,标准输出指向命令使用的管道grep
  • Grep 进行通信以从管道中获取标准输出。

我喜欢这种方式,因为它是用subprocess接口轻轻包裹的自然管道概念。

于 2015-02-10T10:50:04.947 回答
2

之前的答案错过了一个重要的点。正如geocar所指出的,更换shell管道基本上是正确的。在管道的最后一个元素上运行几乎就足够了。communicate

剩下的问题是将输入数据传递到管道。对于多个子流程,communicate(input_data)最后一个元素上的简单操作不起作用 - 它永远挂起。您需要像这样手动创建一个管道和一个孩子:

import os
import subprocess

input = """\
input data
more input
""" * 10

rd, wr = os.pipe()
if os.fork() != 0: # parent
    os.close(wr)
else:              # child
    os.close(rd)
    os.write(wr, input)
    os.close(wr)
    exit()

p_awk = subprocess.Popen(["awk", "{ print $2; }"],
                         stdin=rd,
                         stdout=subprocess.PIPE)
p_sort = subprocess.Popen(["sort"], 
                          stdin=p_awk.stdout,
                          stdout=subprocess.PIPE)
p_awk.stdout.close()
out, err = p_sort.communicate()
print (out.rstrip())

现在子进程通过管道提供输入,父进程调用communication(),按预期工作。使用这种方法,您可以创建任意长的管道,而无需求助于“将部分工作委托给 shell”。不幸的是,子流程文档没有提到这一点。

有一些方法可以在没有管道的情况下达到相同的效果:

from tempfile import TemporaryFile
tf = TemporaryFile()
tf.write(input)
tf.seek(0, 0)

现在stdin=tf使用p_awk. 这是你喜欢什么口味的问题。

以上仍然不是 100% 等效于 bash 管道,因为信号处理不同。如果您添加另一个截断输出的管道元素sort,例如,您可以看到这一点head -n 10。使用上面的代码,sort将打印“Broken pipe”错误消息到stderr. 当您在 shell 中运行相同的管道时,您不会看到此消息。(这是唯一的区别,结果stdout是一样的)。原因似乎是 python 的Popen设置SIG_IGNSIGPIPE,而 shell 将其设置为SIG_DFL, 和sort的信号处理在这两种情况下是不同的。

于 2017-11-18T15:50:43.897 回答
1

编辑: pipes在 Windows 上可用,但至关重要的是,它似乎并不能在 Windows 上实际工作。请参阅下面的评论。

Python 标准库现在包含pipes用于处理此问题的模块:

https://docs.python.org/2/library/pipes.html , https://docs.python.org/3.4/library/pipes.html

我不确定这个模块已经存在了多久,但这种方法似乎比使用subprocess.

于 2014-12-04T18:51:58.210 回答
1

对我来说,下面的方法是最干净、最容易阅读的

from subprocess import Popen, PIPE

def string_to_2_procs_to_file(input_s, first_cmd, second_cmd, output_filename):
    with open(output_filename, 'wb') as out_f:
        p2 = Popen(second_cmd, stdin=PIPE, stdout=out_f)
        p1 = Popen(first_cmd, stdout=p2.stdin, stdin=PIPE)
        p1.communicate(input=bytes(input_s))
        p1.wait()
        p2.stdin.close()
        p2.wait()

可以这样调用:

string_to_2_procs_to_file('input data', ['awk', '-f', 'script.awk'], ['sort'], 'output.txt')
于 2019-06-10T21:00:09.337 回答