5

これはhereからのフォローアップの質問です。


行きたいところ

Pythonがまだ標準出力に出力できる間に、標準出力を一時ファイルに一時的にリダイレクトできるようにしたいと考えています。これには、次の手順が含まれます。

  1. stdout のコピーを作成します ( new)
  2. 一時ファイルを作成する ( tmp)
  3. stdout をリダイレクトするtmp
  4. newstdout として使用するように Python に指示する
  5. tmp「実際の」stdout にリダイレクトする
  6. 「本物の」stdout を再度使用するように Python に指示します。
  7. 読んで閉じるtmp

実装

上記を次の方法で実装しようとしました。

import os
import subprocess
import sys

#A function that calls an external process to print to stdout as well as
#a python print to pythons stdout.
def Func(s, p = False):
    subprocess.call('echo "{0}"'.format(s), shell = True)
    if p:
        print "print"

sil = list() # <-- Some list to store the content of the temp files

print "0.1" # Some testing of the
Func("0.2") # functionality

new = os.dup(1)    # Create a copy of stdout (new)
tmp = os.tmpfile() # Create a temp file (tmp)

os.dup2(tmp.fileno(), 1)            # Redirect stdout into tmp
sys.stdout = os.fdopen(new, 'w', 0) # Tell python to use new as stdout

Func("0.3", True) # <--- This should print "0.3" to the temp file and "print" to stdout

os.dup2(new, 1)                   # Redirect tmp into "real" stdout
sys.stdout = os.fdopen(1, 'w', 0) # Tell python to use "real" stdout again

# Read and close tmp
tmp.flush()
tmp.seek(0, os.SEEK_SET)
sil.append(tmp.read())
tmp.close()

ここで少し休憩してまとめたいと思います。
ここまでのコンソールへの出力は次のようになります。

0.1
0.2
print

whilesil次のようになります['0.3\n']。したがって、ここまですべてが魔法のように機能しています。ただし、上記のスクリプトをもう一度やり直すと、次のようになります。

print "1.1" # Some testing of the
Func("1.2") # functionality

new = os.dup(1)    # Create a copy of stdout (new)
tmp = os.tmpfile() # Create a temp file (tmp)

os.dup2(tmp.fileno(), 1)            # Redirect stdout into tmp
sys.stdout = os.fdopen(new, 'w', 0) # Tell python to use new as stdout

# This should print "0.3" to the temp file and "print" to stdout and is the crucial point!
Func("1.3", True) 

os.dup2(new, 1)                   # Redirect tmp into "real" stdout
sys.stdout = os.fdopen(1, 'w', 0) # Tell python to use "real" stdout again

# Read and close tmp
tmp.flush()
tmp.seek(0, os.SEEK_SET)
sil.append(tmp.read())

エラーが発生し、出力は次のようになります。

1.1
1.2
/bin/sh: line 0: echo: write error: Bad file descriptor
print

whilesil読み取り: ['0.3\n', ''].

つまり、2 番目Func("1.3", True)は一時ファイルに書き込むことができません。

質問

  1. まず、スクリプトが動作したいように動作しない理由を知りたいです。つまり、一時ファイルへの書き込みがスクリプトの前半でしかできないのはなぜですか?
  2. dupとの使い方にはまだ少し戸惑っていますdup2。stdout の一時ファイルへのリダイレクトがどのように機能しているかは理解していると思いますが、なぜos.dup2(new, 1)それが何をしているのかが完全にわかりました。おそらく、答えは、スクリプト内のすべてのdupand dup2s が何をしているのかについて詳しく説明できるかもしれません^^
4

1 に答える 1

13

「不正なファイル記述子」を取得する理由は、ガベージ コレクターが stdout FD を閉じるためです。次の 2 行を検討してください。

sys.stdout = os.fdopen(1, 'w', 0)    # from first part of your script
...
sys.stdout = os.fdopen(new, 'w', 0)  # from second part of your script

これら 2 つのうちの 2 番目が実行されると、最初のファイル オブジェクトの参照カウントがゼロになり、ガベージ コレクターによって破棄されます。ファイル オブジェクトは、破棄されると関連する fd を閉じます。その fd はたまたま 1 = stdout になります。したがって、 で作成されたオブジェクトを破棄する方法には十分注意する必要がありますos.fdopen

問題を示す小さな例を次に示します。os.fstat閉じたfdを渡すと「Bad file descriptor」エラーをトリガーする関数の例として使用されています。

import os
whatever = os.fdopen(1, 'w', 0)
os.fstat(1)
del whatever
os.fstat(1)

私は実際に、あなたが探しているものとまったく同じ(または少なくともほとんど、私の場合は名前付きの一時ファイルが必要)だと思うコンテキストマネージャーを持っています。クローズの問題を回避するために、元の sys.stdout オブジェクトを再利用していることがわかります。

import sys
import tempfile
import os

class captured_stdout:
    def __init__(self):
        self.prevfd = None
        self.prev = None

    def __enter__(self):
        F = tempfile.NamedTemporaryFile()
        self.prevfd = os.dup(sys.stdout.fileno())
        os.dup2(F.fileno(), sys.stdout.fileno())
        self.prev = sys.stdout
        sys.stdout = os.fdopen(self.prevfd, "w")
        return F

    def __exit__(self, exc_type, exc_value, traceback):
        os.dup2(self.prevfd, self.prev.fileno())
        sys.stdout = self.prev

## 
## Example usage
##

## here is a hack to print directly to stdout
import ctypes
libc=ctypes.LibraryLoader(ctypes.CDLL).LoadLibrary("libc.so.6")
def directfdprint(s):
    libc.write(1, s, len(s))


print("I'm printing from python before capture")
directfdprint("I'm printing from libc before captrue\n")

with captured_stdout() as E:
    print("I'm printing from python in capture")
    directfdprint("I'm printing from libc in capture\n")

print("I'm printing from python after capture")
directfdprint("I'm printing from libc after captrue\n")

print("Capture contains: " + repr(file(E.name).read()))
于 2012-01-11T19:29:11.040 に答える