перенаправление powershell stderr прерывает строки каждые несколько символов - PullRequest
0 голосов
/ 29 октября 2018

ОБНОВЛЕНИЕ: это, кажется, характерно для необработанных исключений внутри функции, вызываемой потоком слотов / сигналов PyQt5 (PyQt 5.11 работает в Python 3.7.0). См. ОБНОВЛЕНИЕ ниже этого текста вопроса.


Общая цель состоит в том, чтобы выводить stdout и stderr программы на python на экран и в один и тот же файл на компьютере с Windows 10; Я бы предпочел сделать это перенаправление на уровне операционной системы (то есть оболочка, которая вызывает Python - в данном случае Windows); сделать это чисто изнутри программы на python не получилось.

Проверьте сломанные линии перенаправления stderr ниже.

radiolog.py - большая программа на Python, которая не должна быть здесь уместной; внутри него я запускаю исключение, чтобы проверить этот рабочий процесс регистрации. radiolog_log.ps1 - это оболочка powershell для radiolog.py:

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>

Еще одна попытка - запустить файл .bat из терминала powershell, а затем выполнить его в powershell.

radiolog_log.bat:

python -u radiolog.py 2>&1

В терминале powershell:

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 написан в юникоде, т.е. не читается человеком как простой текстовый файл. Microsoft так много говорит в документации Tee здесь - не знаю, почему ...? Нашел вопрос о том, как преобразовать его в ascii на лету, но, похоже, вы не можете иметь один и тот же файл и вывод в реальном времени, что в первую очередь сводит на нет значительную часть рассуждений о тройнике.

Есть какие-нибудь идеи о том, как заставить все играть просто так, как в Linux?


ОБНОВЛЕНИЕ: слот / поток сигналов PyQt5 , кажется, вызывает это поведение. Пример кода, массово урезанный от оригинального 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-обработчика в соответствии с самым ранним ответом на этот вопрос (не требуется наблюдать прерывистые линии, но он делает более чистый вывод без заголовка-исключения powershell, а также делает простой текстовый файл журнала), затем выполните следующее:

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

Нажмите кнопку в графическом интерфейсе, и вы получите это:

[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, который я скоро соберу и приведу здесь перекрестную ссылку - хотя помощь по этой теме все равно будет отличной!

Ответы [ 2 ]

0 голосов
/ 01 ноября 2018

Частичное бинго - некоторые другие вопросы говорят о том, как PyQt5 обрабатывает необработанные исключения. Не совсем уверенный во взаимодействиях между обработкой stderr в PyQt и обработкой stderr в PowerShell, но переопределение sys.excepthook в python для «старого» поведения делает свое дело. Вот новый полный код Python r.py - единственными изменениями в обновленном коде вопроса являются 'def exc_hook' и 'sys.excepthook = exc_hook' в предложении 'if name ':

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).

Итак, было бы замечательно, если бы кто-нибудь мог пролить больше света на то, почему прерывистый частичный разрыв все еще происходит, но, я думаю, это реальное решение. Спасибо

0 голосов
/ 30 октября 2018

Я пытался повторить эту проблему с помощью какого-то фиктивного скрипта, но в обоих случаях получал надлежащие выходные данные файла и терминала (ps и bat). Я полагаю, вы можете просто использовать свой скрипт bat, а затем исправить кодировку файла в конце. Если вам действительно нужно обновить файл в режиме реального времени, ниже представлен возможный обходной путь. По сути, вы можете создать собственный «Tee-Object» и обрабатывать стандартный вывод. $ _ - это объект, поступающий из конвейера (stdout), даже если он не работает, вы можете исправить кодировку или удалить символы новой строки внутри блока процесса

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"

...