Kann man den Inhalt des CMD-Fensters am Ende eines ausgeführten Skriptes (.cmd) in eine Textdatei kopieren?
Ich habe ein Skript (.cmd), das verschiedene Befehle ausführt und deren Ausgaben im CMD-Fenster anzeigt. Am Ende der Ausführung möchte ich den gesamten Inhalt des CMD-Fensters in eine Textdatei kopieren, um die Ergebnisse später analysieren zu können. Gibt es eine Möglichkeit, dies automatisch zu tun, ohne den Inhalt manuell zu kopieren und einzufügen? Falls ja, wie kann ich das in meinem Skript umsetzen?
Im gezeigten Bild sieht man als Beispiel worauf ich hinaus möchte.
Als Ergänzung
2 Antworten
Am Ende des Scripts kannst Du nicht den bereits (standardmäßig) in den Fenstrpuffer geschriebenen Text in eine Datei kopieren. Es gibt (in Batch) kein Kommando welches (nachträglich ) auf den in die Console geschriebenen Text zugreifen kann.
cmd verfügt auch nicht über das von @JanaL161 erwähnte Tee-Kommando, welches die Ausgabe auf mehrere Outputs verteilen könnte. Allerdings würde Dir dieses auch nicht helfen, wenn es darum geht einen Fehler zu loggen, welcher einen Abbruch der Batch bewirkt. (Wird die Batch wegen eines Fehlers abgebrochen, werden auch alle Ausgabeumleitungen beendet)
Um "nicht abbrechende Fehler" zu loggen ohne jede Zeile einzeln umzuleiten, kannst Du alle funktionalen Zeilen in eine Subroutine verlegen und deren kompletten Output (Errorstream und Standardstream) in eine Datei umleiten. Anschließend gibst Du den Inhalt der Datei komplett im Fenster aus.
demo.cmd
@echo off
rem Ausgabe (Errorstream und Standardstream) der aufgerufenen Subroutine in eine Datei umleiten
call :FunctionalCode 2>&1 >"log.txt"
rem Inhalt der Log-Datei anzeigen
type "log.txt"
pause
exit /b
:FunctionalCode
set "Quellpfad=c:\beispieldatei.txt"
set "Zielpfad=d:\beispieldatei.txt"
echo "%Quellpfad%" -^> "%Zielpfad%"
copy "%Quellpfad%" "%Zielpfad%"
exit /b
Ein gleichzeitige Ausgabe auf den Bildschirm und in eine Datei ist nicht möglich, da der Commandointerpreter (cmd.exe) immer singltreaded arbeitet und einen Output erst ausgibt/weiterleitet, wenn das zuvor aufgerufene Kommando seine Arbeit beendet.
In Deinem Beispielcode sind die angegebenen Pfad falsch deklariert!
falsch:
set Quellpfad="c:beispieldatei.txt"
- im angegebenen Pfad fehlt der Backslash nach dem "c:"
- Auch wenn es zulässig ist einer Variable einen Pfad samt Quotes zu setzen, kann dies im weiteren Verlauf des Scripts zum Problem werden. Nach Möglichkeit sollten Pfade immer ohne umgebende Quotes gespeichert werden. Dazu übergibt man set die gesamte Deklaration innerhalb Quotes.
richtig:
set "Quellpfad=c:\beispieldatei.txt"
...soviel zu "Normal"...
bei mir gilt nachwievor : "Geht nicht gibts nicht"
...man kann auch zaubern und in einer Sprache (welche auf der Fensterbuffer zugreifen kann) einen eigenen Befehl programmieren. Der Einfachheit halber verwende ich Powershell:
demo.ps1
"Keine Gewähr, das dies auch in dem von Dir verwendeten neuen Windows-Terminal klappt!"@'
zur Demo
das ist ein
mehrzeiliger text
'@ # nur damit wir etwas haben wa kopiert werden kann
# prüfe ob wir auf den original ConsoleHost zugreifen.
if ($host.Name -ne 'ConsoleHost'){
write-host -ForegroundColor Red "This script runs only in the console host. You cannot run this script in $($host.Name)."
pause
exit -1
}
# ermittle die Daten des Consolefensters
$bufferWidth = $host.ui.rawui.BufferSize.Width
$bufferHeight = $host.ui.rawui.CursorPosition.Y # nur bis zur aktuellen Position des TextCursors in der Console lesen!!!! (sonst könnte es zu recursionen kommen)
$rec = new-object System.Management.Automation.Host.Rectangle 0,0,($bufferWidth - 1),$bufferHeight
$buffer = $host.ui.rawui.GetBufferContents($rec)
# iteriere über Zeilen/Spalten des buffers
for($i = 0; $i -lt $bufferHeight; $i++){
# initisalisiere für jede Zeile einen neuen string builder.
$textBuilder = new-object system.text.stringbuilder
for($j = 0; $j -lt $bufferWidth; $j++) {
$cell = $buffer[$i,$j] #lies Zeichen an der Position
$null = $textBuilder.Append($cell.Character) #füge Zeichen zu unserem String
}
$textBuilder.ToString().TrimEnd(' ') # Zeile ausgeben. !!!!wichtig Leerzeichen am Ende einer Zeile abschneiden! der FensterpufferBreite wird immer mit mit Leerzeichen aufgefüllt
}
pause
Wie bekommt man das Script jetzt in eine Batch oder die cmd-Console? Ganz einfach... (solange man unter 8000 Zeichen bleibt) als Powershell-Einzeiler. (ich habe alles ein gekürzt was nicht nötig ist, solche BefehlsMonster kann sowieso niemand lesen):
powershell -c "if ($host.Name -ne 'ConsoleHost'){write-host $('This script runs only in the console host. You cannot run this script in {0}.'-f $host.Name) -fo red;pause;exit -1};$RUI=$host.ui.rawui;$BW=$RUI.BufferSize.Width;$BH=$RUI.CursorPosition.Y;$rec=new-object System.Management.Automation.Host.Rectangle 0,0,($BW-1),$BH;$Bfr=$RUI.GetBufferContents($rec);for($i=0;$i -lt $BH;$i++){$TB=[text.stringbuilder]::new();for($j=0;$j -lt $BW;$j++){$null=$TB.Append(($Bfr[$i,$j]).Character)};$TB.ToString().TrimEnd(' ')}"
Jetzt tackere ich da ganze mal zu einer Batch zusammen:
Demo.cmd
@echo off
set "Quellpfad=c:\beispieldatei.txt"
set "Zielpfad=d:\beispieldatei.txt"
echo blubb %time%
echo "%Quellpfad%" -^> "%Zielpfad%"
copy "%Quellpfad%" "%Zielpfad%"
rem fieser Powershell One-Liner!
rem Lies den Zeilenpuffer der Console aus und screibe diesen in die datei "log.txt"
>>"log.txt" powershell -c "if ($host.Name -ne 'ConsoleHost'){write-host $('This script runs only in the console host. You cannot run this script in {0}.'-f $host.Name) -fo red;pause;exit -1};$RUI=$host.ui.rawui;$BW=$RUI.BufferSize.Width;$BH=$RUI.CursorPosition.Y;$rec=new-object System.Management.Automation.Host.Rectangle 0,0,($BW-1),$BH;$Bfr=$RUI.GetBufferContents($rec);for($i=0;$i -lt $BH;$i++){$TB=[text.stringbuilder]::new();for($j=0;$j -lt $BW;$j++){$null=$TB.Append(($Bfr[$i,$j]).Character)};$TB.ToString().TrimEnd(' ')}"
rem die Datei mal im Standard-Editor öffnen
start "" "log.txt"
pause
...das ist natürlich nur Spielerei (machbar, aber sinnlos). Wenn man schon in einer mächtigeren Sprache programmieren kann, ist es Quatsch sich mit Batch herumzuschlagen! Wenn die Ausgabe vor dem Auslesen des Fensterpuffers dessen voreingestellte Größe überschreitet, werden die ersten Zeilen gelöscht. (also man kann programmtechnisch nur lesen, was man auch mit den Augen lesen könnte)
Wenn Du was richtiges machen möchtest, machs gleich mit Powershell, dann muss man sich nicht das Hirn verrenken um mit irgendwelchen Krücken zu hantieren.
1..100|
ForEach-Object{
'{0:D3} Missisippi'-f $_
}|
Tee-Object 'pslog.txt'
pause
Am Ende nochmal alles zu kopieren ist eine seltsame Herangehensweise. Viel einfacher ist es doch, die Ausgabe von Anfang an schon beim Aufruf per ">" in eine Datei umzuleiten.
Wenn es um die Fehlermeldung geht; die ist standardmäßig von der normalen Ausgabe getrennt aufgeführt, um damit nicht zu kollidieren.
Mit 2>&1 kannst du sowohl die normale als auch die Fehlerausgabe in dieselbe Datei schreiben
Siehe auch: https://de.wikipedia.org/wiki/Standard-Datenstr%C3%B6me
Mit tee kannst du Ströme an mehrere Orte gleichzeitig senden (z. B. eine Datei und die Ausgabe): https://learn.microsoft.com/de-de/powershell/module/microsoft.powershell.utility/tee-object?view=powershell-7.4
Dadurch sollte nichts mehrfach ausgeführt werden. Die Daten werden nur anders verarbeitet um eben das gewünschte zu erzielen.
1:1 wie das Feature aus der Konsole wird schwer, weil du auf den internen Puffer nicht zugreifen kannst. Somit müssen die Ausgabe-Streams der Programme abgefangen werden.
So wie es die Möglichkeit ja bereits von Windows gibt.
Es macht einen gewaltigen Unterschied ob man von "Außen" auf den angezeigten Text in einem Fenster zugreift, oder innerhalb eines Programms auf eine komplexe Struktur wie den Fensterbuffer der Console.
Der Programmcode "sieht nicht das was Du zu lesen bekommst sondern ein lineare Abfolge von Bytes, welche weit mehr beinhaltet als nur ein paar Buchstaben
Ein Zeichen im Buffer besteht aus Character, Vordergrund- und Hintergrund-Farbinformation, Type. https://learn.microsoft.com/en-us/dotnet/api/system.management.automation.host.buffercell?view=powershellsdk-1.1.0
mal die Interna eine 40x7 Pufferchens mit Text als Json darstellen...
demo.ps1
mode 40,6 #kleines Fenster einrichten
@'
zur Demo
das ist ein
mehrzeiliger text
'@
$bufferWidth = $host.ui.rawui.BufferSize.Width
$bufferHeight = $host.ui.rawui.CursorPosition.Y
$rec = new-object System.Management.Automation.Host.Rectangle 0,0,($bufferWidth - 1),$bufferHeight
$buffer = $host.ui.rawui.GetBufferContents($rec)
$buffer|ConvertTo-Json >>'ConsolebufferStruct-Json.txt'
start 'blubb.txt' #datei im Editor anzeigen
pause
...das liegt weit Jenseits dessen was man mit Batch bewirken könnte. ...oder was man im normalen Leben jemals gebrauchen könnte. (das letzte mal, das ich dergleichen nötig hatte, war in den frühen 90ern mit Pascal)
Im Nachhinein die Ausgabe zu kopieren ist dabei der Designfehler. Stell dir vor du bekommst Arbeitsaufträge im Job. Am Ende des Tages sagt dir der Chef dann, dass du Minuten genau protokollieren sollst, was du wann gemacht hast.
Wenn man vorher sagt, ist es trivial. Und so ist die Umleitung per 2>&1 der Ausgabe in ein File einfach und das Kopieren der Ausgabe nach Ablauf des Programms nahe dran an Raketentechnologie. Das würde die Komplexität des Codes verfielfachen.
Ich habe es allerdings nur Zeileartig hinbekommen mit ">". Ich möchte ja aber alles bekommen.