Mit PowerShell in Dateien schreiben: Redirect, tee, Out-File, Set-Content


    Tags: ,

    In Datei speichernEin gängiges Anliegen beim Arbeiten auf der Kommandozeile besteht darin, dass man die Ausgabe von Befehlen in eine Datei schreiben möchte. PowerShell unterstützt wie schon cmd.exe die Umleitung der Ausgabe, jedoch mit mehr Optionen. Hinzu kommen Cmdlets, die noch weitere Features bieten.

    Egal ob man nur die Ausgabe eines Cmdlets, das Ergebnis von komplexen Berechnungen oder die Log-Informationen eines Scripts permanent speichern möchte, in der Regel benötigt man dafür Mechanismen, um die Ausgabe von der Konsole in eine Datei umzuleiten.

    Einfache Umleitung der Ausgabe

    Diese Aufgabe erledigen die Redirect-Operatoren, deren Verwendung in einfachen Fällen gleich ist wie unter der altbekannten Eingabeaufforderung. Soll nur die planmäßige und erfolgreiche Ausgabe eines Befehls in eine Datei umgelenkt werden, dann reicht die Verwendung von ">" und ">>":

    Get-Process > process.out

    Sollte in diesem Beispiel process.out bereits bestehen, dann würde ihr bisheriger Inhalt durch den Redirect überschrieben. Dagegen würde

    Get-Process >> process.out

    diesen bewahren, indem die neuen Daten an die vorhandenen angehängt werden.

    Fünf Streams umleitbar

    Neben den eigentlichen Nutzdaten geben die meisten Cmdlets und Scripts weitere Informationen aus, nämlich beim Auftreten von Fehlern und Warnungen, beim Debuggen und Statusmeldungen über die Arbeitsfortschritte.

    Wenn ein Script seine Daten über die dafür vorgesehenen Kanäle ausgeben soll, dann muss sich sein Programmierer an einige Regeln halten. Das heißt insbesondere, dass er die passenden Cmdlets verwendet, um die Informationen in den richtigen Stream zu schreiben, etwa Write-Debug, Write-Verbose (etwa für eine Fortschrittsanzeige) oder Write-Warning.

    Während cmd.exe nur STDERR an ein anderes Gerät umleiten kann, lässt PowerShell dies für alle 5 genannten Streams zu. Dafür muss man den Redirect-Operator um die Nummer des Kanals ergänzen, also zum Beispiel

    Get-Process 5> debug.out

    Dieser Aufruf würde die Liste der laufenden Prozesse wie gewohnt am Bildschirm anzeigen, während die Debug-Informationen in debug.out landen. Möchte man diese an die bestehende Datei anhängen, dann verwendet man stattdessen 5>>.

    Bei der gezielten Umleitung einzelner Streams verwendet man folgende numerischen Werte:

    1. 1 Erfolgreiche Ausgabe des planmäßigen Inhalts
    2. 2 Fehler
    3. 3 Warnungen
    4. 4 Verbose (also Metadaten zum Ablauf eines Scripts)
    5. 5 Debug-Nachrichten

    Umleitung auf andere Streams

    Möchte man den Output eines Kanals nicht in eine Datei umlenken, sondern auf einen anderen Stream, dann erwartet der Redirect-Operator folgende Syntax:

    m>&n

    Dabei steht m für den Stream, dessen Ausgabe umgeleitet werden soll, und n für das Ziel der Ausgabe. So würde

    Get-Process 5>&1

    die Debug-Informationen dort ausgeben, wo der reguläre Output von Get-Process angezeigt wird.

    Wenn nicht nur einzelne, sondern alle Streams umgeleitet werden sollen, dann lässt PowerShell dafür die Verwendung von Wildcards zu:

    Get-Process *> process.out

    Dies funktioniert nach dem gleichen Muster auch für >>, wenn man die Ausgabe an die Datei anhängen will. Ebenso zulässig ist das Umlenken aller Streams auf einen einzigen, etwa in der Form

    Get-Process *>&1

    Fortführung der Pipeline mit tee

    Leitet man die Ausgabe eines Befehls in eine Datei um, dann steht der Output nicht mehr zur Verfügung, um ihn via Pipe an ein Cmdlet durchzureichen und dort weiterzuverarbeiten. Um beim obigen Beispiel zu bleiben: Wenn man die ganze Ausgabe von Get-Process in eine Datei schreiben, aber am Bildschirm nur die Attribute ID, Name und CPU sehen möchte, dann würde folgender Aufruf nicht funktionieren:

    Get-Process > process.out | select ID, Name, CPU

    In diesem Fall käme bei select nichts an, entsprechend wäre seine Ausgabe leer. Abhilfe schafft hier das Tee-Objekt, das den Output wie sein Vorbild unter Unix aufspaltet. Auf diese Weise gelangt er in eine Datei und gleichzeitig auf die Konsole, von wo er mittels Pipe weitergegeben werden kann:

    Get-Process | tee process.out | select ID, Name, CPU

    Am Bildschirm zeigt sich nur der gefilterte Output, nachdem alle Informationen in die Datei geschrieben wurden.

    Erweitere Funktionen mit Out-File

    In den meisten Fällen erweist sich ein Redirect per ">" oder ">>" als ausreichend, um die gewünschten Informationen in eine Datei zu schreiben. Die Operatoren bieten neben der reinen Umleitung aber nicht viele Optionen. Diese beschränken sich auf das Anhängen versus Überschreiben vorhandener Daten und die Angabe der Zieldatei.

    Möchte man aber einen anderen Zeichensatz verwenden als Unicode, die Zeilenbreite der Ausgabe anpassen oder in schreibgeschützte Dateien schreiben, dann bietet sich das Cmdlet Out-File als Alternative an. Es akzeptiert die Eingabe entweder über eine Pipe oder als Kommando bzw. Variable, die man dem Parameter InputObject übergibt:

    Out-File -FilePath .\temp.out -Width 256 -InputObject $(Get-Process) -Encoding ascii -Force

    Dieser Aufruf würde die Ausgabe von Get-Process in die Datei .\temp.out schreiben, dabei den ASCII-Zeichensatz verwenden und die Zeilenbreite auf 256 Zeichen erweitern, so dass in der tabellarischen Darstellung keine Informationen abgeschnitten werden.

    Dabei würde ein eventuell vorhandener Inhalt von .\temp.out ohne Warnung überschrieben, und zwar auch dann, wenn für die Datei das Attribut read-only gesetzt ist (wegen des Schalters -Force). Dies kann man mit dem Parameter NoClobber verhindern, die Ausführung des Befehls wird dann abgebrochen, wenn die Datei schon existiert.

    Will man jedoch die Ausgabe eines Cmdlets oder eines Scripts an eine bestehende Datei anhängen, muss man den Schalter Append verwenden.

    Ein äquivalenter Aufruf zum obigen Beispiel bestünde darin, dass man Get-Process eigenständig ausführt und seinen Output an Out-File mittels Pipe weiterleitet:

    Get-Process | Out-File temp.out -Width 256 -Encoding ascii -Force

    In mehrere Dateien schreiben

    Sowohl die Redirect-Operatoren als auch Out-File akzeptieren für die Dateinamen keine Wildcards, so dass sie immer nur in eine Datei schreiben können. Für die meisten Anwendungen reicht das aus, etwa um den Output eines Kommandos zu speichern.

    Will man dagegen den Inhalt von mehreren bestehenden Textdateien aktualisieren, dann eignen sich dafür die Cmdlets Set-Content und Add-Content. Das erste ersetzt den Inhalt der angegebenen Dateien, während das zweite die angegebenen Daten an ihren bestehenden Inhalt anhängt.

    Set-Content hat jedoch nicht die Mittel, um ausgewählte Informationen in den Dateien zu verändern, sondern schreibt diese komplett neu auf Basis dessen, was man über den Parameter -Value angibt. Möchte man daher nur bestimmte Zeichenketten ersetzen, dann muss man alle Dateien erst mit Get-Content einlesen, mit Hilfe einfacher Ersetzungsmuster oder regulärer Ausdrücke bearbeiten und abschließend mit Set-Content wieder zurückschreiben.

    Einfacher ist es dagegen, wenn man nur Informationen an bestehende Dateien anhängen möchte. In diesem Fall übergibt man an Add-Content einen oder mehrere durch Komma getrennte Dateinamen (Parameter -Path), wobei auch Wildcards zulässig sind. Anschließend schreibt es den in -Value spezifizierten Wert an das Ende aller Files:

    Add-Content *.out, *.xml -Value $(Get-Date)

    Dieser Aufruf würde am Ende aller Dateien mit der Endung .out und .xml das aktuelle Datum plus Uhrzeit einfügen.

    Täglich Know-how für IT-Pros mit unserem Newsletter

    Wir ver­wenden Ihre Mail-Adresse nur für den Ver­sand der News­letter.
    Es erfolgt keine per­sonen­be­zogene Auswertung.

    Bild von Wolfgang Sommergut
    Wolfgang Sommergut hat lang­jährige Erfahrung als Fach­autor, Berater und Kon­ferenz­sprecher zu ver­schie­denen Themen der IT. Da­ne­ben war er als System­ad­mi­ni­stra­tor und Con­sultant tätig.
    // Kontakt: E-Mail, XING, LinkedIn //

    Verwandte Beiträge

    Weitere Links