Kann man den Inhalt des CMD-Fensters am Ende eines ausgeführten Skriptes (.cmd) in eine Textdatei kopieren?


13.01.2025, 10:10

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
Woher ich das weiß:eigene Erfahrung – Ich mach das seit 30 Jahren

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.


knies 
Beitragsersteller
 13.01.2025, 10:04

Ich habe es allerdings nur Zeileartig hinbekommen mit ">". Ich möchte ja aber alles bekommen.

mjutu  13.01.2025, 10:05
@knies

Was bedeutet "Zeilenartig"? Zeige doch mal ein Beispiel, was du genau gemacht hast und was du erreichen willst.

knies 
Beitragsersteller
 13.01.2025, 10:12
@mjutu

Ich habe dem Beitrag ein sehr vereinfachtes Beispiel beigefügt. Den Text aus der cmd möchte ich dann sozusagen in Zeile 12 in eine .txt oder .log exportieren um im nachhinein alles prüfen zu können.

knies 
Beitragsersteller
 13.01.2025, 10:15
@knies

Also ich möchte zum einen den Text in der cmd anzeigen und zusätzlich in einer .txt abspeichern.

knies 
Beitragsersteller
 13.01.2025, 10:30
@JanaL161

Ich möchte es aber nur 1x ausführen, weshalb ich am besten wahrscheinlich den Konsoleninhalt am Ende in eine .txt exportieren müsste. So wie es die Möglichkeit ja bereits von Windows gibt. (siehe erstes Bild)

JanaL161  13.01.2025, 10:33
@knies

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.

Erzesel  13.01.2025, 18:45
@knies
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)

mjutu  13.01.2025, 22:13
@knies

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.