PythonのDup、dup2、tmpfile、stdout
-
27-10-2019 - |
質問
これはからのフォローアップの質問です ここ.
行きたいところ
PythonはまだSTDOUTに印刷することができますが、STDOUTを一時的にTEMPファイルにリダイレクトできるようにしたいと思います。これには、次の手順が含まれます。
- stdoutのコピーを作成します(
new
) - 温度ファイルを作成します(
tmp
) - stdoutをリダイレクトします
tmp
- Pythonに使用するように伝えます
new
stdoutとして - リダイレクト
tmp
「本物の」stdoutに - Pythonに「本物の」stdoutをもう一度使用するように伝えます
- 読んで閉じる
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
その間 sil
このように見えるはずです: ['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
その間 sil
読む: ['0.3\n', '']
.
つまり、2番目 Func("1.3", True)
Tempファイルに書き込むことができません。
質問
- まず第一に、私のスクリプトが動作したいように私のスクリプトが機能していない理由を知りたいです。つまり、なぜスクリプトの前半でTempファイルに書き込むことができるのですか?
- 私はまだの使用に少し困惑しています
dup
とdup2
. 。 stdoutの一時ファイルへのリダイレクトがどのように機能しているかを理解していると思いますがos.dup2(new, 1)
それがしていることをやっています。たぶん、答えはすべてを詳しく説明できるかもしれませんdup
とdup2
私のスクリプトのsは^^をやっています
解決
「悪いファイル記述子」を取得する理由は、Garbage Collectorが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を渡すと、「悪いファイル記述子」エラーをトリガーする例の機能として使用されます。
import os
whatever = os.fdopen(1, 'w', 0)
os.fstat(1)
del whatever
os.fstat(1)
私は実際に、あなたが探していることを正確に行うと思うコンテキストマネージャー(または、ほぼ少なくとも、私はたまたま名付けられたtempfileが必要です)を持っていると思います。密接な問題を回避するために、元の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()))