如何在Python中无缝地包装Bash shell IO

How to seamlessly wrap Bash shell IO in Python

如何在Python脚本中包装bash shell会话,以便Python可以将stdout和stderr存储到数据库中,偶尔写入stdin?

我尝试使用带有类似tee的Python类的子进程来重定向IO,但它似乎使用fileno完全绕过Python。

shell.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import os
import sys
from StringIO import StringIO
from subprocess import Popen, PIPE

class TeeFile(StringIO):
    def __init__(self, file, auto_flush=False):
        #super(TeeFile, self).__init__()
        StringIO.__init__(self)
        self.file = file
        self.auto_flush = auto_flush
        self.length = 0

    def write(self, s):
        print 'writing' # This is never called!!!
        self.length += len(s)
        self.file.write(s)
        #super(TeeFile, self).write(s)
        StringIO.write(self, s)
        if self.auto_flush:
            self.file.flush()

    def flush(self):
        self.file.flush()
        StringIO.flush(self)

    def fileno(self):
        return self.file.fileno()

cmd = ' '.join(sys.argv[1:])
stderr = TeeFile(sys.stderr, True)
stdout = TeeFile(sys.stdout, True)

p = Popen(cmd, shell=True, stdin=PIPE, stdout=stdout, stderr=stderr, close_fds=True)

例如 运行python shell.py ping google.com运行正确的命令并显示输出,但Python从未看到stdout。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#!/usr/bin/env python

# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.

from twisted.internet import protocol
from twisted.internet import reactor
import re

class MyPP(protocol.ProcessProtocol):
    def __init__(self, verses):
        self.verses = verses
        self.data =""
    def connectionMade(self):
        print"connectionMade!"
        for i in range(self.verses):
            self.transport.write("Aleph-null bottles of beer on the wall,
"
+
                                "Aleph-null bottles of beer,
"
+
                                "Take one down and pass it around,
"
+
                                "Aleph-null bottles of beer on the wall.
"
)
        self.transport.closeStdin() # tell them we're done
    def outReceived(self, data):
        print"outReceived! with %d bytes!" % len(data)
        self.data = self.data + data
    def errReceived(self, data):
        print"errReceived! with %d bytes!" % len(data)
    def inConnectionLost(self):
        print"inConnectionLost! stdin is closed! (we probably did it)"
    def outConnectionLost(self):
        print"outConnectionLost! The child closed their stdout!"
        # now is the time to examine what they wrote
        #print"I saw them write:", self.data
        (dummy, lines, words, chars, file) = re.split(r'\s+', self.data)
        print"I saw %s lines" % lines
    def errConnectionLost(self):
        print"errConnectionLost! The child closed their stderr."
    def processExited(self, reason):
        print"processExited, status %d" % (reason.value.exitCode,)
    def processEnded(self, reason):
        print"processEnded, status %d" % (reason.value.exitCode,)
        print"quitting"
        reactor.stop()

pp = MyPP(10)
reactor.spawnProcess(pp,"wc", ["wc"], {})
reactor.run()

这是将命令IO作为协议处理的Twisted方式。 顺便说一下,你的脚本也很复杂。 而是检查Popen.communicate()方法。 请注意,stdin / stdout是文件描述符,需要并行读取,因为如果输出更长,它们的缓冲区将会溢出。 如果你想通过这个流式传输大量数据,而是使用那种Twisted方式,或者在Popen方式激活单独的线程来读取stdout的情况下可以通过行并立即将它们放到DB中。
关于过程协议的扭曲的howto。