powershell stderr 重定向每隔几个字符换行

powershell stderr redirect breaks lines every few characters

更新: 这似乎特定于 PyQt5 的 slot/signal 流程调用的函数内未捕获的异常(PyQt 5.11 运行ning 在 Python 3.7.0)。请参阅此问题文本下方的更新。


总体目标是在 Windows 10 机器上将 python 程序的 stdout 和 stderr 发送到屏幕和同一文件;我更愿意在操作系统级别执行此重定向(即在本例中调用 python - Windows 的 shell);从 python 程序中干净地完成它无论如何都没有成功。

查看下面损坏的 stderr 重定向行。

radiolog.py 是一个很大的 python 程序,在这里不应该是相关的;在其中我触发了一个异常以测试这个日志记录工作流。 radiolog_log.ps1 是 radiolog.py:

的强大 shell 包装器
python -u radiolog.py 2>&1 | % ToString | Tee-Object log.txt

运行 radiolog_log.ps1 来自 powershell 终端:

PS C:\Users\caver\Documents\GitHub\radiolog> .\radiolog_log.ps1
6319:Operating system is Windows.
6319:PowerShell.exe is in the path.
... (omitting a bunch of irrelevant radiolog transcript output)
6329:Accepted2
Traceback (most recent call last):
  File "
radiolog.py", line 3796, in keyPress
Eve
n
t
    sel
f.accept
(
)
  File "
r
adio
l
og.
py"
,
 line 3
9
13, in accept
    rprint(1/0)
ZeroD
ivi
sionError: division by zero

PS C:\Users\caver\Documents\GitHub\radiolog>

有一些关于 powershell stderr 重定向在控制台宽度处分割线的好帖子和答案...然而,这个似乎每隔几个字符分割线,它是每次都不一样

的| % ToString 摆脱了(在本例中)冗余的 powershell 异常处理层;没有 | % ToString 它看起来像这样(所有以 'python: Traceback' 开头的都是红色文本):

6851:Accepted2
python : Traceback (most recent call last):
At line:1 char:1
+ python -u radiolog.py 2>&1
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (Traceback (most recent call last)::String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError

  F
ile "radiolog.py", line 3796, in keyPressEvent
    self.accept()
  File "ra
diol
o
g.p
y",
lin
e 39
13, in accept
    rp
r
int(1/0)
Ze
r
oDivisio
n
Error:
div
i
sion by zero
PS C:\Users\caver\Documents\GitHub\radiolog>

另一种尝试是从 powershell 终端 运行 一个 .bat 文件,然后在 powershell.

中做发球

radiolog_log.bat:

python -u radiolog.py 2>&1

在电源shell终端:

PS C:\Users\caver\Documents\GitHub\radiolog> .\radiolog_log.bat | % ToString | Tee-Object log.dat

这导致以下终端显示 - 一切如预期:

C:\Users\caver\Documents\GitHub\radiolog>python -u radiolog.py  2>&1
8314:Operating system is Windows.
8314:PowerShell.exe is in the path.
...
8327:Accepted2
Traceback (most recent call last):
  File "radiolog.py", line 3796, in keyPressEvent
    self.accept()
  File "radiolog.py", line 3913, in accept
    rprint(1/0)
ZeroDivisionError: division by zero
PS C:\Users\caver\Documents\GitHub\radiolog>

但是 log.dat 是用 unicode 编写的,也就是说,作为纯文本文件,人类无法阅读。 Microsoft 在 Tee 文档中说了很多 here - 不确定为什么......?发现了一个关于将其即时转换为 ascii 的问题,但是,看起来您不能拥有相同的实时文件和终端输出,首先否定了 tee 的大部分推理。

关于如何让一切都像在 linux 中那样发挥得很好有什么想法吗?


更新:PyQt5 slot/signal flow 似乎触发了此行为。示例代码,从原始 radiolog.py:

大幅缩减
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

import sys

class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")
        self.pushButton = QPushButton(Dialog)
        self.pushButton.setObjectName("pushButton")
        self.pushButton.setText(" Add Entry ")
        self.horizontalLayout = QHBoxLayout(Dialog)
        self.horizontalLayout.addWidget(self.pushButton)

class MyWindow(QDialog,Ui_Dialog):
    def __init__(self,parent):
        QDialog.__init__(self)
        self.ui=Ui_Dialog()
        self.ui.setupUi(self)

# click the button to see that stderr inserts line breaks every few
#   characters (different on each occurrance) when openNewEntry is called
#   from the slot/signal flow:      
        self.ui.pushButton.clicked.connect(self.openNewEntry)

# uncomment the following line to see that stderr is line-buffered when
#  openNewEntry is called from code:
#       self.openNewEntry()

    def openNewEntry(self):
        print(1/0)

def main():
    app = QApplication(sys.argv)
    w = MyWindow(app)
    w.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

在 powershell 终端中,按照这个问题的最早答案定义 STD-Handler 后(不需要观察虚线,但是,它确实在没有 power[=84 的情况下使输出更清晰) =]-exception header以及制作纯文本日志文件),然后执行如下:

python -u r.py *>&1 | STD-Handler log.txt

单击 GUI 中的按钮,您将得到:

[636765635572699130]: Traceback (most recent call last):
[636765635572808866]:
[636765635572918571]:  File "r.py", line 33,
[636765635573028268]:
[636765635573118026]: in open
[636765635573207784]: New
[636765635573307659]: E
[636765635573407558]: ntry
    print(1/0)
ZeroDivisionError: division by z
[636765635573506983]: ero

现在取消注释 python 代码中的行,以便从代码而不是从用户交互中调用异常; 运行 在 powershell 终端的同一行,你会得到这个:

[636765639350787977]: Traceback (most recent call last):
[636765639350877842]:   File "r.py", line 42, in <module>

[636765639350997857]:
[636765639351077838]: main()
  File "r.py", line 37, in main

[636765639351187538]:
[636765639351282570]: w = MyWindow(app)
  File "r.py", line 30, in __init__

[636765639351372787]:
[636765639351467301]: self.openNewEntry()
  File "r.py", line 33, in openNewEntry

[636765639351554768]:
[636765639351660016]: print(1/0)
ZeroDivisionError: division by zero

因此,这可能值得向 PyQt 社区提出一个新问题,我将很快将其放在一起并在此处进行交叉引用 - 尽管对此线程的帮助仍然很棒!

我试图用一些虚拟脚本重现这个问题,但在这两种情况下(ps 和 bat)都获得了正确的文件和终端输出。我想你可以只使用你的 bat 脚本,然后在最后修复文件编码。如果您真的需要实时更新文件,下面是一个可能的解决方法。基本上你可以创建一些自定义 "Tee-Object" 并在它出现时处理 stdout。 $_ 是来自管道 (stdout) 的对象,即使它损坏了,您也可以修复编码或删除 Process 块中的换行符

function STD-Handler
{  param ($outFile)

    Begin { $null > $outFile }

    Process  {

    $line = "[" + (get-date).Ticks + "]: " + $_ 
     Write-Host $line
     $line >> $outFile

    }

    End  { <# can do smth with the file at the end#>}
}

用法:

python -u C:\stack\stdout.py *>&1 | STD 处理程序 "C:\temp\log.txt"

Partial Bingo - 其他一些问题讨论了 PyQt5 处理未捕获异常的方式。不太确定 PyQt 的 stderr 处理和 powershell 的 stderr 处理之间的交互,但是,覆盖 python 中的 sys.excepthook 以获得 'old' 行为就可以了。这是新的整个 r.py python 代码 - 更新后的问题代码的唯一变化是 'if name' 子句中的 'def except_hook' 和 'sys.excepthook = except_hook':

from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

import sys

class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")
        self.pushButton = QPushButton(Dialog)
        self.pushButton.setObjectName("pushButton")
        self.pushButton.setText(" Add Entry ")
        self.horizontalLayout = QHBoxLayout(Dialog)
        self.horizontalLayout.addWidget(self.pushButton)

class MyWindow(QDialog,Ui_Dialog):
    def __init__(self,parent):
        QDialog.__init__(self)
        self.ui=Ui_Dialog()
        self.ui.setupUi(self)

# click the button to see that stderr inserts line breaks every few
#   characters (different on each occurrance) when openNewEntry is called
#   from the slot/signal flow:      
        self.ui.pushButton.clicked.connect(self.openNewEntry)

# uncomment the following line to see that stderr is line-buffered when
#  openNewEntry is called from code:
#       self.openNewEntry()

    def openNewEntry(self):
        print(1/0)

def except_hook(cls, exception, traceback):
    sys.__excepthook__(cls, exception, traceback)
    exit(-1)

def main():
    app = QApplication(sys.argv)
    w = MyWindow(app)
    w.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    sys.excepthook = except_hook
    main()

现在,单击按钮会产生以下输出:

[636766143015640817]: Traceback (most recent call last):
[636766143015760489]:   File "r.py", line 33, in openNewEntry

[636766143015911442]:
[636766143016031102]: print(1/0)
ZeroDivisionError: division by zero

有时(不重复)它在标点符号等方面有点断裂,但是,这更易于管理:

[636766144719906619]: Traceback (most recent call last):
[636766144720006506]:   File "r.py", line 47, in <module>

[636766144720096055]:
[636766144720195662]: main()
  File "r.py", line 41, in main

[636766144720275744]:
[636766144720375114]: w = MyWindow(app)

[636766144720469728]:   File "r.py", line 30, in __init__

[636766144720565688]:
[636766144720657318]: self.openNewEntry()
  File "r.py", line 33, in openNewEntry

[636766144720757052]:
[636766144720852021]: print(1/0)
ZeroDivisionError
[636766144720946788]: :
[636766144721046802]: division by zero
[636766144721132731]:

而且这种表现更好的输出也出现在完整程序中 (radiolog.py)。

所以,如果有人能更清楚地说明为什么仍然会发生间歇性部分压裂,那就太好了,但是,我认为这是一个可行的解决方案。谢谢