Darstellungsproblem nach Auslesen einer PowerShell Pipe

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Hallo zusammen,

ich habe ein Problem, das nur bei Verwendung der Windows PowerShell auftritt. Es geht darum, Daten von der Standardeingabe zu lesen und korrekt anzuzeigen.

Hier ein kleines Testprogramm (lauffähig unter Python 2 und Python 3), um das Problem besser nachvollziehen zu können:

Code: Alles auswählen

from __future__ import print_function
import sys

def print_stream(input_stream):
    if hasattr(input_stream, 'buffer'):
        # `buffer` is expected to provide the raw bytes.
        # Prefer this over pre-encoded strings.
        input_stream = input_stream.buffer
    result = input_stream.read().rstrip()
    if hasattr(sys.stdout, 'encoding') and isinstance(result, bytes):
        # Python 3 prints `repr(result)` if `result` is a bytes type.
        # Avoid that by converting `result` into a string.
        result = result.decode(sys.stdout.encoding)
    print(result)


if __name__ == '__main__':
    print_stream(sys.stdin)
Das Programm dürfte wohl selbsterklärend sein.

Zum Testen führe ich das Programm via ``echo späm | py -2 print_input.py`` bzw ``echo späm | py -3 print_input.py`` aus.

Bei Verwendung von `cmd.exe` erscheint erwartungsgemäß "späm" auf dem Bildschirm. Nutze ich jedoch die PowerShell, dann erscheint stattdessen "sp?m". Ganz offensichtlich gibt es hierbei also an irgendeiner Stelle ein Problem mit Umlauten, oder allgemeiner gesagt: mit Zeichen außerhalb von ASCII.

Eines noch als Anmerkung: Nach entsprechender Recherche habe ich herausgefunden, dass man dieses Problem umgehen kann, wenn man vorher den Befehl ``$OutputEncoding = [Console]::OutputEncoding`` ausführt. Wenn ich es richtig verstanden habe, dann nutzt die PowerShell für interne Umleitungen eine andere Kodierung als für die spätere Ausgabe. Mit anderen Worten: Das, was aus der Pipe kommt, ist anders kodiert als das, was am Ende in der Shell angezeigt wird. Und diese Diskrepanz gleicht man durch den besagten Befehl wieder aus.

Meine Frage ist nun: Wie kann ich innerhalb von Python (d.h. ohne vorherigen Shell-Befehl) dafür sorgen, dass auch Zeichen außerhalb von ASCII korrekt in der PowerShell angezeigt werden, wenn sie aus einer Pipe gelesen werden? Naheliegend wäre es wohl, das "OutputEncoding" auszulesen und dessen Wert beim Dekodieren in Python zu verwenden, damit man gar nicht erst etwas an dieser Variable verändern muss. Leider habe ich aber bisher nicht herausgefunden, wie dies funktioniert. Als "normale" Umgebungsvariable in `os.environ` taucht es zumindest nicht auf. Oder gibt es noch andere Vorschläge?

Für Hilfe wäre ich dankbar... :)
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@snafu:
Was gibt denn `sys.stdin.encoding` mit der Umleitung über die Powershell an? Wenn das korrekt gesetzt wird, sollte es möglich sein, dass innerhalb von Python zu lösen. Falls nicht, wirds IMHO schwierig, da Du dann nur die Chance über Manipulation der Powershell-Settings hast. Davon weisst Dein Pythonskript aber nichts - heisst, Du müsstest es auf höherer Ebene, wo Du die Verkettung machst, festlegen und Python z.B. als Parameter mitgeben. Also irgendwas dergestalt (hab keine Ahnung von der Powershell-Syntax):

Code: Alles auswählen

$OutputEncoding = [Console]::OutputEncoding
commandA | pythonskript --encoding=$OutputEncoding
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

jerch hat geschrieben:Was gibt denn `sys.stdin.encoding` mit der Umleitung über die Powershell an?
Das ist leider auf `None` gesetzt.

So wie ich das sehe, bleibt nur die Möglichkeit, zu erkennen, ob das Programm innerhalb einer PowerShell-Sitzung gestartet wurde. Dann könnte man zumindest via `subprocess` den Befehl ``powershell -Command "Auslesen des Encodings"`` absetzen (wobei ich das konkrete Kommando noch recherchieren müsste). Man hätte zwar dann nur die Einstellung beim Starten einer neuen PowerShell-Sitzung herausgefunden, aber hier ließe sich ja die von dir vorgeschlagene Option einbauen, damit der Benutzer ggf auch eine eigene Angabe für das Encoding machen kann, falls sich das Encoding nachträglich geändert hat.

Oder man probiert es via `ctypes`, falls es einen geeigneten Aufruf in der Windows-API gibt, um die Kodierung der Pipe auzulesen. Falls das sogar universell für PowerShell und cmd.exe möglich sein sollte, dann würde ich diese Lösung sogar bevorzugen.

Ich werde mich in den nächsten Tagen nochmal genauer damit befassen. Hilfreiche Vorschläge sind nach wie vor willkommen. :)
BlackJack

@snafu: Das hat nichts mit der PowerShell zu tun sondern grundsätzlich mit Pipes und Umleitungen in oder aus Dateien. Da kann man, also Python, unmöglich erraten welche Kodierung da kommt oder erwartet wird. Das ist unter `cmd.exe` oder unter Linux/Unix genau so. Die Frage ist ja nicht ob das unter einer PowerShell läuft, sondern welche Kodierung das Programm vor der Pipe ausgibt. Du müsstest also im konkreten Fall herausfinden dass das ``echo`` ist, und dann was *das* als Kodierung für seine Ausgabe verwendet. Nun nehmen wir aber mal den allgemeinen Fall an: ``parrot -x 42 --frobnicate=true blah | your_python_program.py`` — wie soll Python denn hier herausfinden können in welcher Kodierung das Programm ``parrot`` Bytes auf seiner Standardausgabe ausgibt‽ Da *muss* man dem Benutzer die Möglichkeit geben das explizit anzugeben, damit er das Python-Programm mit jedem beliebigen anderen Programm per Pipe kombinieren kann.

Ich persönlich schreibe Programme die Ein- oder Ausgabe (de)kodieren meistens so dass sie standardmässig UTF-8 verwenden, weil damit alles kodiert werden kann was man als Unicode-Zeichenkette im Programm haben kann. Damit ist man vor Ausnahmen sicher das etwas nicht kodiert werden kann. Und dann biete ich dem Benutzer meistens eine Möglichkeit explizit eine andere Kodierung zu wählen.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Vielleicht nochmal zum "$OutputEncoding": Das steht nach dem Starten der PowerShell auf "us-ascii". Damit kann man natürlich schlecht Umlaute anzeigen. Ich frage mich nur, wie "echo" das hinbekommt. Denn wenn ich einfach ein ``echo "späm"`` absetze, dann funktioniert die Anzeige problemlos. Erst wenn ich die Ausgabe in mein Programm umleite, kommt es zu dem Darstellungsproblem. Und wie gesagt nur in einer PowerShell-Umgebung. Unter cmd.exe klappt es.

Die Frage ist ja auch, welche Kodierung der Benutzer bei einer "-encoding"-Option angeben soll, wenn doch am Ende doch alles wieder auf ASCII-Basis angezeigt wird. Ich befürchte, dieses Problem ist schlichtweg pythonseitig nicht lösbar. Man muss wohl mit den Mitteln der PowerShell für Abhilfe sorgen. Zum Beispiel, indem man den oben genannten Befehl ausführt, um den Wert für "$OutputEncoding" zu ändern.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Jeffrey Snover (Microsoft-Entwickler mit wesentlichem Anteil an der PowerShell, u.a. das Pipelining) hatte sich vor Jahren mal in einem Blog-Beitrag zu dem Thema geäußert. Ganz unten schreibt er:
POSTSCRIPT: The reason we convert to ASCII when piping to existing executables is that most commands today do not process UNICODE correctly. Some do, most don't.
Quelle: http://blogs.msdn.com/b/powershell/arch ... escue.aspx

Der Beitrag ist von 2006 und bisher scheint man nicht von der Meinung abgerückt zu sein, dass ASCII als Default-Encoding sinnvoller ist, anstatt einfach UTF-8 (oder ggf UTF-16 für die Windows-Welt) zu nehmen...
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

BlackJack hat geschrieben:@snafu: Das hat nichts mit der PowerShell zu tun sondern grundsätzlich mit Pipes und Umleitungen in oder aus Dateien.
Für mich deutet vieles darauf hin, dass es sich durchaus um ein hausgemachtes Problem der PowerShell handelt. Du hast Recht, wenn du schreibst, dass es kein "Pipe-Encoding" gibt, sondern dass es sich dabei um die Kodierung handelt, die das ausgebende Programm selbst gewählt hat. Letztlich sorgen aber wohl die spezifischen Mechanismen der PowerShell zumindest bei Cmdlets dafür, dass man standardmäßig ASCII beim Auslesen der Pipe erhält. Unter cmd.exe passiert das nicht, denn da gibt es bekanntlich noch keine Cmdlets.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

snafu hat geschrieben:Du hast Recht, wenn du schreibst, dass es kein "Pipe-Encoding" gibt, sondern dass es sich dabei um die Kodierung handelt, die das ausgebende Programm selbst gewählt hat.
Nein, die Bsp. Deiner verlinkte Seite zeigen, dass das cmdlet-Encoding schon bei einer simplen `commandA | commandB` als "Pipe-Encoding" aktiv ist.

Und was spricht jetzt dagegen, dass auf der Powershellebene, also dort wo es verursacht wird, zu beheben? Wenn Du kein encoding Parameter haben willst, dann nimm halt als Voreinstellung utf-8:

Code: Alles auswählen

PS C:\> $OutputEncoding = New-Object -typename System.Text.UTF8Encoding
PS C:\> echo bläöüß | python -c "import sys;print sys.stdin.read().decode('utf-8')"
bläöüß
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@jerch: Die PowerShell wandelt ja nicht grundsätzlich alles, was dort durch eine Pipe geht, in ein Standard-Encoding um. Es sind die Cmdlets, die erkennen, dass sie in eine Pipe schreiben und sich dann anders verhalten (vermutlich gesteuert durch einen entsprechenden Bibliotheksaufruf, den alle Cmdlets nutzen). Klassische Programme, die keine Cmdlets sind, aber in der PowerShell aufgerufen werden, verhalten sich nicht so.
jerch hat geschrieben:Und was spricht jetzt dagegen, dass auf der Powershellebene, also dort wo es verursacht wird, zu beheben?
Es spricht nichts dagegen. Man müsste das als Anwender halt nur selber einstellen. Das habe ich ja auch von Anfang an so erwähnt. Ich dachte nur, man könne dem Anwender dies ersparen. Kann man aber anscheinend nicht.

Die Sache, die mir inzwischen klargeworden ist, ist ja folgende: Wenn die Ausgabe an Python durch die Pipe geleitet wurde, ist sie bereits kaputt. Die Fragezeichen für die Umlaute entstehen nicht etwa durch Python, wie ich zunächst annahm, sondern die werden schon seitens des Kodierers der PowerShell gesetzt. Es ist also unmöglich, nachträglich aus Python heraus zu erkennen, welches Zeichen ursprünglich mal anstelle des Fragezeichens stand. Python kommt an der Stelle schlichtweg "zu spät", um noch etwas retten zu können.

Das Aha-Erlebnis war für mich der Moment, an dem ich gesehen hatte, dass die Ausgabe eines Cmdlets standardmäßig in ASCII erfolgt. Hilfreich war auch der Hinweis von BlackJack, dass die Vorstellung eines "Pipe-Encodings", das grundsätzlich für PowerShell-Pipes gelten würde, falsch ist, sondern dass nur die Kodierung zählt, die das Programm für seine eigene Ausgabe nutzt.

Also für mich sind nun alle Fragen geklärt. Vielen Dank für eure Hilfe. :)
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@snafu:
Ich hatte es nur kurz getestet mit echo + Python und Python + Python. Bei einer Python-zu-Python-Pipe hat dieses ominöse Pipe-Encoding sehr seltsame Auswirkungen. Letztendlich habe ich es dafür nur mit UTF-32 halbwegs verlässlich hinbekommen:

Code: Alles auswählen

PS C:\> $OutputEncoding = New-Object -typename System.Text.UTF32Encoding
PS C:\> python -c "print u'äöüß'.encode('utf-32')" | python -c "import sys; print sys.stdin.read().decode('utf-32')"
äöüß
Damit lassen sich darstellbare Zeichen von links nach rechts "retten". Mit Utf-8 klappt das nicht mehr und ich hab absolut keine Ahnung, was da eigentlich vor sich geht. Ein 8Bit-Encoding lässt sich für $OutputEncoding leider auch nicht als "Bytecontainer" für beliebige Formate setzen, das gibt es schlichtweg nicht.

Was seltsam ist an dem Python-Python Bsp - wenn die Pipe kein "Encoding" für Executables links und rechts der Pipe hat, warum ändert das Setzen von $OutputEncoding bei angeglichenem Output- und Inputencoding das Ergebnis?

Diese halb kaputt wirkende Unicodeunterstützung im Jahre 2015 ist irgendwie traurig.
BlackJack

@jerch: Was heisst denn „Mit Utf-8 klappt das nicht mehr“? Datenmüll oder Ausnahme? Kann es sein das Microsoft bei UTF8 ein BOM davor klatscht?
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@jerch: Du hast Recht. Das merkwürdige Verhalten wirkt sich anscheinend tatsächlich auf alle Pipes in der PowerShell aus. Habe selten sowas dämliches gesehen.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

BlackJack hat geschrieben:@jerch: Was heisst denn „Mit Utf-8 klappt das nicht mehr“? Datenmüll oder Ausnahme? Kann es sein das Microsoft bei UTF8 ein BOM davor klatscht?
Die Frage ging zwar an jerch, aber ich antworte trotzdem mal: Mit UTF-8 werden falsche Zeichen angezeigt, jedoch nicht das böse Fragezeichen. Ich verwende da lieber ``sys.stdout.encoding``, welches bei mir auf "cp850" eingestellt ist.

Die nachfolgende Ausgabe ist aus meiner `cmd.exe` unter Verwendung von Python 2.7:

Code: Alles auswählen

C:\>python -c "print 'späm'"
spõm

C:\>python -c "print u'späm'"
späm

C:\>python -c "print u'späm'.encode('utf-8')"
späm

C:\>python -c "print u'späm'.encode('cp850')"
späm

C:\>python -c "print u'späm'.encode('cp850')" | python -c "import sys; print sys.stdin.read().rstrip()"
späm
Nun in der PowerShell:

Code: Alles auswählen

PS C:\> python -c "print 'späm'"
spõm
PS C:\> python -c "print u'späm'"
späm
PS C:\> python -c "print u'späm'.encode('utf-8')"
späm
PS C:\> python -c "print u'späm'.encode('cp850')"
späm
PS C:\> python -c "print u'späm'.encode('cp850')" | python -c "import sys; print sys.stdin.read().rstrip()"
sp?m
Also exakt gleich, außer dass die Pipe eben das "ä" verschluckt.
BlackJack

@snafu: Aber jerch hat doch die Kodierung der PowerShell auf UTF8 gestellt und *das* sollte dann ja nicht das 'ä' durch ein Fragezeichen ersetzen.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Ok Doku lesen hilft ;) Die Powershell ist prinziell Unicode und nutzt für Stringrepräsentationen die Stringklasse aus .net (Microsoft nennt das unsinnigerweise ANSI, was UTF-16 ist). Dh. alles innerhalb von Cmdlets ist Unicode. Tricky wirds, wenn Pipes dazukommen. Dann entscheidet die rechte Seite, ob ein Encoder mitläuft oder nicht:

Ist die rechte Seite eine Datei (bei > Pipes) oder ein anderes Cmdlet, wird das .net-intern als Unicode weiterverarbeitet.

Ist die rechte Seite ein Programm, wird der Encoder dazwischen gehängt. Standardmässig wird ASCII kodiert (sinngemässe Erklärung der Entwickler hierzu - "die meisten Programme kommen mit Unicode nicht klar"), und das Verhalten kann man auf UTF-8, Unicode (UTF-16) und UTF-32 umstellen.

Wichtig ist, dass die Einstellung $OutputEncoding nicht das Encoding der Powershell umstellt, sondern wirklich nur den Output des Encoders im Falle der Pipe mit Programm rechts. Das führt dazu, dass der Output rechts der Pipe wiederum auf die 8bit encodings angewiesen ist (meist CP850). Man man, wer sich das ausgedacht hat...
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

jerch hat geschrieben:Man man, wer sich das ausgedacht hat...
Zumal das IMHO ein Rückschritt ist. Unter `cmd.exe` gibt es ja auch schon Pipes. Und da funktioniert es mit dem Standardverhalten, sofern man Bytes in der richtigen Kodierung übergibt.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@snafu:
Ja mir ist das "dumme" Bytestreamverhalten der POSIX-Welt auch lieber. Aus Shell/Konsolensicht wird das Encoding zum PAL (Problem anderer Leute Feld) und die eingesetzten Kommandos können die Sache untereinander aushandeln. Aber im "do one thing and do it right" liegt ja gerade die Stärke der Unix-Kommandozeilenhelferlein. Wenn ich mir anschaue, welchen Siegeszug die unixoiden Systeme in den letzten 15 Jahren hingelegt haben, versteh ich nicht, warum Microsoft das lausige SFU lieber abschaffte anstatt auf eine besser integrierte POSIX-Abstraktion zu setzen. Inklusive gutem Terminal + mächtiger Shell und nicht dem cmd.exe/Powershell-Murks. Microsoft war noch nie gut im Abschneiden alter Zöpfe, wahrscheinlich liegt das CP/M-Erbe selbst heutigen Windows-Inkarnationen noch schwer im Magen. :twisted:

Ok genug Rant eines POSIX Fanboys ;)
Antworten