Ersetzen von Zahlenfolgen in Dateien - CMD
Hallo,
ich bräuchte ein wenig Hilfe.
Ich müsste mittels CMD-Batch eine Zahlenfolge in einer XML (bzw. währendessen ist es eine ".error"-Datei) ersetzen.
Bisher habe ich nur die String mit findstr gefunden und bearbeiten können, aber wie kann ich an der Stelle die neue Zahlenfolge einfügen?
Mein bisheriger Code:
Wahrscheinlich ist dies wieder leichter mit Powershell umzusetzen, aber da hab ich noch fast keine Erfahung damit (CMD wär mir lieber, wenn möglich)
Meine Überlegung wäre, wenn es nicht an der gleichen Stelle ersetzt werden kann, dann könnte man den Teil davor und danach in ein Temp-File speichern, genau so wie die Zeile die ersetzt wird und nachher alle zusammenfügen.
ich bräuchte ein wenig Hilfe.
Ich müsste mittels CMD-Batch eine Zahlenfolge in einer XML (bzw. währendessen ist es eine ".error"-Datei) ersetzen.
Bisher habe ich nur die String mit findstr gefunden und bearbeiten können, aber wie kann ich an der Stelle die neue Zahlenfolge einfügen?
Mein bisheriger Code:
@ECHO off
SETLOCAL enabledelayedexpansion
echo falsche IRN eingeben...
set /p IRN=
echo richtige Pruefziffer eingeben...
set /p PZ=
for /f "tokens=1,2,* delims=:" %%a in ('findstr /ilc:"%IRN%" "C:\Temp\PruefzifferBatch\TEST.xml.error"') do set "IRN2=%%c"
set IRN2=%IRN2:~28,11%
set IRN3=%IRN2%%PZ%
echo ------
echo %IRN3%
pause
Wahrscheinlich ist dies wieder leichter mit Powershell umzusetzen, aber da hab ich noch fast keine Erfahung damit (CMD wär mir lieber, wenn möglich)
Meine Überlegung wäre, wenn es nicht an der gleichen Stelle ersetzt werden kann, dann könnte man den Teil davor und danach in ein Temp-File speichern, genau so wie die Zeile die ersetzt wird und nachher alle zusammenfügen.
Bitte markiere auch die Kommentare, die zur Lösung des Beitrags beigetragen haben
Content-ID: 1094386490
Url: https://administrator.de/contentid/1094386490
Ausgedruckt am: 22.11.2024 um 08:11 Uhr
22 Kommentare
Neuester Kommentar
Zitat von @Haberl92:
ein weiteres Problem ist der Dateiname.
Der Name ist immer ein andererer. Das Script soll am besten alle .error-Dateien durchsuchen nach der eingegebenen IRN.
Bei Batch kann ich mit Platzhalter ("?") arbeiten, aber wie ist das bei Powershell
ein weiteres Problem ist der Dateiname.
Der Name ist immer ein andererer. Das Script soll am besten alle .error-Dateien durchsuchen nach der eingegebenen IRN.
Bei Batch kann ich mit Platzhalter ("?") arbeiten, aber wie ist das bei Powershell
Da kannst Du mit regulären Ausdrücken arbeiten. Viel besser. Viel flexibler. Und sexy.
Bei Deinem XML fehlt leider die Hälfte oder mehr. Lies Dir mal verschiedene Seiten durch, die sich mit dem Thema PS und XML beschäftigen. Das ist mehr als sexy. Kurz beschrieben: Du liest ein XML-Dokument als XML-Objekt ein und hast somit über die Objekteigenschaften Zugriff auf jedes einzelne Element des XML. Guck z. B. mal hier (so auf die Schnelle ergoogelt): https://www.windowspro.de/script/xml-powershell-elemente-attribute-ausle .... Dann änderst Du den Wert und schreibst ihn zurück. Alles ist gut.
Moin,
hier kommst du mit dem replace-Operator und regulären Ausdrücken leichter und sauberer ans Ziel.
Der Punkt steht für "beliebiges Zeichen", das Dollarzeichen für "am Ende der Zeichenkette".
Regex Tutorial
Regex Legende
Get-Content gibt nur den Inhalt einer Textdatei wieder. Du musst also in der Foreach-Schleife die einzelnen Dateien abarbeiten.
In deinem Fall wäre es am besten, die Dateien mit Select-String auf die Zeichenkette durchsuchen. So erhälst du auch wirklich nur die Dateien, die die Zeichenkette enthalten.
Gruß Thomas
hier kommst du mit dem replace-Operator und regulären Ausdrücken leichter und sauberer ans Ziel.
$IRN2 = $IRN -replace '.$',$PZ
Regex Tutorial
Regex Legende
Get-Content"C:\Temp\PruefzifferBatch\*.error"
In deinem Fall wäre es am besten, die Dateien mit Select-String auf die Zeichenkette durchsuchen. So erhälst du auch wirklich nur die Dateien, die die Zeichenkette enthalten.
# $IRN in .error-Dateien suchen
$Result = Select-String -SimpleMatch $IRN -Path "C:\Temp\PruefzifferBatch\*.error" -List
# Für jede gefundene Datei
Foreach ($File in $Result) {
# Dateiinhalt einlesen
$Content = Get-Content $File.Path
# $IRN durch $IRN2 ersetzen
$Content = $Content.replace($IRN, $IRN2)
# Datei speichern
$Content | Set-Content $File.Path
} # Ende Foreach
Gruß Thomas
Moin,
IMHO nicht. Ich würde Dir empfehlen, dass mit den XML-Funktionen zu machen und nicht mit blindem Durchsuchen der Datei nach der Ziffernfolge und dem blinden Ersetzen. Ich weiß ja nicht, wie die Namen da so entstehen. Aber ich würde befürchten, dass die Ziffernfolge auch mal zufällig an anderer Stelle z. B. in der Image-ID auftaucht. Die würde beim blinden Ersetzen auch mit geändert und dann ist alles kaputt. Mit den XML-Funktionen kannst Du sicher sein, dass Du auch wirklich nur Daten des Knoten veränderst, den Du auch wirklich manipulieren willst.
Na indem Du am Ende das Ergebnis in die Datei schreibst.
Liebe Grüße
Eriki
Zitat von @Haberl92:
bin ich da schon auf den richtigen Weg?
$IRN = Read-Host "falsche IRN eingeben"
> $PZ = Read-Host "richtige Prüfziffer"
> $IRN2 = $IRN.Remove(11)
> $IRN3 = $IRN2.Insert(11, "$PZ")
> # Write-Host $IRN3
> Get-Content"C:\Temp\PruefzifferBatch\*.error"|Foreach-Object{$_.Replace("$IRN","$IRN3")}|Set-Content"C:\Temp\PruefzifferBatch\Gleicher_Name_wie-Ursprung.xml"
IMHO nicht. Ich würde Dir empfehlen, dass mit den XML-Funktionen zu machen und nicht mit blindem Durchsuchen der Datei nach der Ziffernfolge und dem blinden Ersetzen. Ich weiß ja nicht, wie die Namen da so entstehen. Aber ich würde befürchten, dass die Ziffernfolge auch mal zufällig an anderer Stelle z. B. in der Image-ID auftaucht. Die würde beim blinden Ersetzen auch mit geändert und dann ist alles kaputt. Mit den XML-Funktionen kannst Du sicher sein, dass Du auch wirklich nur Daten des Knoten veränderst, den Du auch wirklich manipulieren willst.
Und wie schaffe ich es, dass die Zieldatei die Ursprungsdatei ersetzt mit gleichen Namen?
Na indem Du am Ende das Ergebnis in die Datei schreibst.
Liebe Grüße
Eriki
Moin,
Ein kleiner Codeschnippsel, um Dich auf die richtige Bahn zu setzen:
Noch ein Hinweis zum Speichern ohne .error:
Die PS ist bekanntlich streng objektorientiert. Das macht die Sache gerade bei solchen Aktionen viel einfacher. In der Schleife steht Dir mit $file ein Objekt zur Verfügung, das alle Eigenschaften der Datei enthält. In der Eigenschaft $file.basename steht der Name ohne Extension. In $file.name steht der Name mit Extension. In $file.fullname steht der ganze Pfad auf die Datei. Und in $file.extension steht die Erweiterung nach dem letzten Punkt. Vielleicht noch $file.parent. Das ist das übergeordnete Verzeichnis. Damit solltest Du das hinkriegen, zum Schluss ohne .error zu speichern.
Liebe Grüße
Erik
Zitat von @Haberl92:
Das klingt schon mal gut. Muss ich nachher gleich testen.
Eine Frage noch:
Die Dateien sind vorher als "Dateiname.xml.error" gespeichert. Ist es möglich die Dateien dann ohne ".error" zu speichern, also als "Dateiname.xml" und die Originaldatei dann löschen? (aber nur bei erfolgreichem ersetzen des Strings)
Und wie sieht es aus, wenn in dem Ordner mehrere ".error"-Dateien sind. Nimmt er dann nur die Datei in der der String steht?
Das klingt schon mal gut. Muss ich nachher gleich testen.
Eine Frage noch:
Die Dateien sind vorher als "Dateiname.xml.error" gespeichert. Ist es möglich die Dateien dann ohne ".error" zu speichern, also als "Dateiname.xml" und die Originaldatei dann löschen? (aber nur bei erfolgreichem ersetzen des Strings)
Und wie sieht es aus, wenn in dem Ordner mehrere ".error"-Dateien sind. Nimmt er dann nur die Datei in der der String steht?
Ein kleiner Codeschnippsel, um Dich auf die richtige Bahn zu setzen:
$errorfiles = get-childitem *.error
foreach ($file in $errorfiles) {
[xml]$xml = get-content $file
# Hier das ganze Gewurschtel mit dem Suchen und Ersetzen im XML
}
Noch ein Hinweis zum Speichern ohne .error:
Die PS ist bekanntlich streng objektorientiert. Das macht die Sache gerade bei solchen Aktionen viel einfacher. In der Schleife steht Dir mit $file ein Objekt zur Verfügung, das alle Eigenschaften der Datei enthält. In der Eigenschaft $file.basename steht der Name ohne Extension. In $file.name steht der Name mit Extension. In $file.fullname steht der ganze Pfad auf die Datei. Und in $file.extension steht die Erweiterung nach dem letzten Punkt. Vielleicht noch $file.parent. Das ist das übergeordnete Verzeichnis. Damit solltest Du das hinkriegen, zum Schluss ohne .error zu speichern.
Liebe Grüße
Erik
Zitat von @erikro:
Moin,
Ein kleiner Codeschnippsel, um Dich auf die richtige Bahn zu setzen:
Noch ein Hinweis zum Speichern ohne .error:
Die PS ist bekanntlich streng objektorientiert. Das macht die Sache gerade bei solchen Aktionen viel einfacher. In der Schleife steht Dir mit $file ein Objekt zur Verfügung, das alle Eigenschaften der Datei enthält. In der Eigenschaft $file.basename steht der Name ohne Extension. In $file.name steht der Name mit Extension. In $file.fullname steht der ganze Pfad auf die Datei. Und in $file.extension steht die Erweiterung nach dem letzten Punkt. Vielleicht noch $file.parent. Das ist das übergeordnete Verzeichnis. Damit solltest Du das hinkriegen, zum Schluss ohne .error zu speichern.
Moin,
Zitat von @Haberl92:
Das klingt schon mal gut. Muss ich nachher gleich testen.
Eine Frage noch:
Die Dateien sind vorher als "Dateiname.xml.error" gespeichert. Ist es möglich die Dateien dann ohne ".error" zu speichern, also als "Dateiname.xml" und die Originaldatei dann löschen? (aber nur bei erfolgreichem ersetzen des Strings)
Und wie sieht es aus, wenn in dem Ordner mehrere ".error"-Dateien sind. Nimmt er dann nur die Datei in der der String steht?
Das klingt schon mal gut. Muss ich nachher gleich testen.
Eine Frage noch:
Die Dateien sind vorher als "Dateiname.xml.error" gespeichert. Ist es möglich die Dateien dann ohne ".error" zu speichern, also als "Dateiname.xml" und die Originaldatei dann löschen? (aber nur bei erfolgreichem ersetzen des Strings)
Und wie sieht es aus, wenn in dem Ordner mehrere ".error"-Dateien sind. Nimmt er dann nur die Datei in der der String steht?
Ein kleiner Codeschnippsel, um Dich auf die richtige Bahn zu setzen:
> $errorfiles = get-childitem *.error
> foreach ($file in $errorfiles) {
>
> [xml]$xml = get-content $file
> # Hier das ganze Gewurschtel mit dem Suchen und Ersetzen im XML
>
> }
>
Noch ein Hinweis zum Speichern ohne .error:
Die PS ist bekanntlich streng objektorientiert. Das macht die Sache gerade bei solchen Aktionen viel einfacher. In der Schleife steht Dir mit $file ein Objekt zur Verfügung, das alle Eigenschaften der Datei enthält. In der Eigenschaft $file.basename steht der Name ohne Extension. In $file.name steht der Name mit Extension. In $file.fullname steht der ganze Pfad auf die Datei. Und in $file.extension steht die Erweiterung nach dem letzten Punkt. Vielleicht noch $file.parent. Das ist das übergeordnete Verzeichnis. Damit solltest Du das hinkriegen, zum Schluss ohne .error zu speichern.
<edit>Das [xml] vor der Variablen ist wichtig. Das weißt die PS an, nicht ein Objekt "textdatei", sondern ein Objekt "xml-Dokument" anzulegen.</edit>
Liebe Grüße
Erik
Zitat von @Haberl92:
Es funktioniert so eigentlich perfekt.
Nun versteh ich das mit dem $File.Path noch nicht.
Mit Powershell arbeitet man Objektorieniert, sprich mit Objekten, die verschiedene Eigenschaften besitzen.Es funktioniert so eigentlich perfekt.
Nun versteh ich das mit dem $File.Path noch nicht.
Stell dir zum Beispiel ein Adressbuch vor. Jeder einzelne Eintrag in dem Adressbuch hat verschiedene Eigenschaften, wie etwa: Name, Vorname, PLZ, Ort, Straße, Hausnummer, ...
Genauso verhält es sich auch bei Powershell.
In diesem Fall gibt der Befehl Select-String immer Objekte der Klasse MatchInfo zurück. Dieses enthält unter anderem die Eigenschaften:
Path | Der Dateipfad, in dem die Zeichenkette vorkommt |
Filename | Der Dateiname |
Line | Die komplette Zeile, in der die gesuchte Zeichenkette vorkommt |
Linenumber | Die Zeilennummer, in der die Zeichenkette gefunden wurde |
Nachdem du also den Select-String Befehl ausgeführt hast, hast du in der Variable $Result Objekte dieser Klasse; und kannst zum Beispiel mit
$Result.Filename
Ebenso kannst du dir die Objekte und ein paar Eigenschaften auch als Tabelle anzeigen lassen:
$Result | Select -Property Linenumer,Filename,Line
in unser Foreach-Schleife bearbeiten wir alle diese Objekte einzeln in der Variable $File. Wenn wir den Inhalt einer Datei laden wollen, brauchen wir den Dateipfad, der in der Path-Eigenschaft steht - daher $File.Path
Noch genauer wird das alles in dem Powershell Leitfaden für Anfänger erläutert.
Wie schaffe ich es, dass es ohne die Endung ".error" gespeichert wird?
# Dateiname neu
$Filename = $File.Filename -replace '\.error$'
# Datei speichern
$Content | Set-Content $Filename
Dann hätte ich noch ein Problem:
Ich habe die Datei mit der Endung ".xml.error" und noch eine weitere Datei mit gleichen Namen aber nur der Endung ".error".
[...]
Wäre es möglich die Variablen aus der anderen Datei auslesen zu lassen?
Ich habe die Datei mit der Endung ".xml.error" und noch eine weitere Datei mit gleichen Namen aber nur der Endung ".error".
[...]
Wäre es möglich die Variablen aus der anderen Datei auslesen zu lassen?
# Quellpfad
$Source = 'C:\Test'
# Alle .error-Dateien Auflisten (ohne xml)
$Err_Files = Get-ChildItem -Path "$Source\*.error" -Exclude '*.xml.error'
# Für jede Error-Dateien
Foreach ($File in $Err_Files) {
# Fehlermeldung mit regulären ausdrücken analysieren
$Fehler = Select-String -Pattern 'BelegRefernzNummer (\d{12}).*?richtig w.re (\d)!' -Path $File
# IRN und Prüfziffer aus Fehlermeldung lesen
$IRN = $Fehler.Matches.Groups[1].Value
$PZ = $Fehler.Matches.Groups[2].Value
# Neue IRN erstellen
$IRN_neu = $IRN -replace '.$',$PZ
# Dateipfad der dazugehörigen .xml.error-Datei
$Xml_Pfad = '{0}\{1}.xml.error' -f $File.DirectoryName, $File.BaseName
# Datei lesen
$Content = Get-Content $Xml_Pfad
# IRN ersetzen
$Content = $Content -replace "(?<=>)$IRN(?=<)",$IRN_neu
# Datei speichern
$Content | Set-Content $Xml_Pfad
# .xml.error-Datei umbenennen in .xml
Rename-Item -Path $Xml_Pfad -NewName ($Xml_Pfad.Name -replace '\.error$')
} # Ende Foreach
Das Ganze enthält bislang natürlich noch keine Fehlerbehandlung, falls etwa eine Datei oder ein Wert nicht gefunden werden konnten - aber ich dachte mir fürs erste reicht das auch mal.
Mein Fehler, sorry.
Der Pfad in $XML_Path ist eine Zeichenkette und kein FileSystemObject, hat daher auch keine Eigenschaft "Name" - diese einfach weglassen, dann passt es.
\d steht für "digit" (also Ziffer) und gilt immer genau für eine Stelle.
Bei der IRN ist {12} eine Mengenangabe, sorgt also dafür, das die IRN immer eine Zahl von exakt 12 Ziffern sein muss.
Darüber hinaus hab ich dir noch eine Fehlerüberprüfung eingebaut, falls die XML-Datei nicht gefunden werden konnte.
Der Pfad in $XML_Path ist eine Zeichenkette und kein FileSystemObject, hat daher auch keine Eigenschaft "Name" - diese einfach weglassen, dann passt es.
Rename-Item -Path $Xml_Pfad -NewName ($Xml_Pfad -replace '\.error$')
Wäre es möglich prüfen zu lassen, dass er als $PZ eine Zahl zwischen 0 und 9 einliest?
Etwas anderes kann er ohnehin nicht einlesen. # Fehlermeldung mit regulären ausdrücken analysieren
$Fehler = Select-String -Pattern 'BelegRefernzNummer (\d{12}).*?richtig w.re (\d)!' -Path $File
\d steht für "digit" (also Ziffer) und gilt immer genau für eine Stelle.
Bei der IRN ist {12} eine Mengenangabe, sorgt also dafür, das die IRN immer eine Zahl von exakt 12 Ziffern sein muss.
Falls es was anderes sein sollte, dann soll er nichts machen?
Geht alles. Sinnvoller ist es hier, den Select-String-Befehl vor die Foreach-Schleife zu legen und dann die Ergebnisse mit Foreach zu bearbeiten. So werden Dateien, bei denen die IRN keine 12-stellige Zahl oder die Prüfziffer keine 1-stellige Zahl ist erst gar nicht abgearbeitet.Darüber hinaus hab ich dir noch eine Fehlerüberprüfung eingebaut, falls die XML-Datei nicht gefunden werden konnte.
# Quellpfad
$Source = 'C:\Test'
# .error-Dateien mit regulären ausdrücken analysieren
$Result = Select-String -Pattern 'BelegRefernzNummer (\d{12}).*?richtig w.re (\d)!' -Path "$Source\*.error" -Exclude "*.xml.error" -List
# Für jede Error-Dateien
Foreach ($File in $Result) {
# Versuche, die dazugehörige XML-Datei zu laden
try { $Xml_Pfad = Get-Item -ErrorAction 'Stop' ($File.Path -replace '(?=\.error$)','.xml') }
# Falls Versuch fehlschlägt, Warnung augeben und Datei überspringen
catch {
Write-host -ForegroundColor 'red' $_.Exception.Message
Continue
}
# IRN und Prüfziffer aus Fehlermeldung lesen
$IRN = $File.Matches.Groups[1].Value
$PZ = $File.Matches.Groups[2].Value
# Neue IRN erstellen
$IRN_neu = $IRN -replace '.$',$PZ
# Datei lesen
$Content = Get-Content $Xml_Pfad.Fullname
# IRN ersetzen
$Content = $Content -replace "(?<=>)$IRN(?=<)",$IRN_neu
try {
# Datei überschreiben
$Content | Set-Content $Xml_Pfad.Fullname -ErrorAction stop
# ".xml.error"-Datei umbenennen in ".xml"
Rename-Item -Path $Xml_Pfad -NewName $Xml_Pfad.BaseName -ErrorAction stop
# .error-Datei löschen
Remove-Item -Path $File.Path -ErrorAction stop
} catch { write-host -ForegroundColor 'red' $_.Exception.Message }
} # Ende Foreach
Zitat von @Haberl92:
Kann ich nach erfolgreichen Ersetzen und Umbenennen die ".error"-Datei (nicht die ".xml.error") gleich löschen lassen?
Kein Problem. Habe es oben noch ergänzt.Kann ich nach erfolgreichen Ersetzen und Umbenennen die ".error"-Datei (nicht die ".xml.error") gleich löschen lassen?
Zitat von @Haberl92:
es scheint noch, als würde eine geschweifte Klammer fehlen.
Die schließende "}" fehlt im Anweisungsblock oder der Typdefinition.
+ CategoryInfo : ParserError: ( , ParentContainsErrorRecordException
+ FullyQualifiedErrorId : MissingEndCurlyBrace
es scheint noch, als würde eine geschweifte Klammer fehlen.
Die schließende "}" fehlt im Anweisungsblock oder der Typdefinition.
+ CategoryInfo : ParserError: ( , ParentContainsErrorRecordException
+ FullyQualifiedErrorId : MissingEndCurlyBrace
Bei allem nötigen Respekt. Aber so eine Klammer selbständig zu setzen, sollte jetzt nicht zum Showstopper werden.