Funktionierender regulärer Ausdruck (CMD) um aus einer Textdatei bestimmte Zeilen mit " zu entfernen?
Eigentlich könnte man ja meinen, dass dieses Problem recht leicht zu lösen sein müsste.
Aber aus irgendwelchen Gründen ist es das nicht, da " irgendwie für alles einen Sonderstatus hat.
Also was ich gerne wissen würde:
Wie bzw. mit welchem regulären Ausdruck kann man (z.b mit einem Grepbefehl?) alle Zeilen suchen, die mit einem " anfangen ohne, dass dies zu unerwarteten Verhalten führt und z.b der Pfad nicht mehr gefunden wird bzw. keine Ausgabedatei erzeugt wird?
Ich hab es jetzt bereits mit dutzenden verschiedensten Varianten versucht: grep '^"' grep '^\"' grep -E '^\"' grep '^\".*'
und noch X weiteren Variationen, aber entweder werden einfach nur alle Zeilen ausgegeben, die irgendwo im Text ein " enthalten oder es gibt Probleme mit der Erzeugung der Ausgabedatei.
Wichtig wäre in diesem Fall jedoch, dass nur der Anfang (erste Charakter) einer Zeile überprüft bzw. gematched werden soll. Scheinbar soll dies bei Grep ja mit ^ möglich sein und ein Escapen der " mit \. Funktioniert aber alles nicht!
GPT hab ich auch schon gefragt, aber der Bot ist mit der Frage komplett überfordert und gibt nur hanebüchenen Mist aus, der entweder nicht funktioniert oder sehr langsam ist (z.b Ansätze mit Powershell)
Also bevor ich jetzt noch meinen kompletten PC / CPU zu Schrott fahre: Gibt es eine effektive und schnelle Möglichkeit wie man diese Operation aus einer Textdatei alle Zeilen ohne " am Anfang = delete (möglichst mit verfügbaren Onboardmitteln) ausführen kann und falls ja wie?
Im Idealfall sollten die Zeilen, die nicht gematched werden auch einfach direkt aus der Datei herausgelöscht werden können, anstatt jedesmal den kompletten Inhalt zu kopieren.
Irgendein Tipp?
4 Antworten
Hallo Zortah,
Du könntest den Inhalt der Textdatei kopieren und in ein Microsoft Excel Sheet Einfügen "als Werte".
Dann hast Du alle Zeilen in Excel.
Da kannst Du dann oben den Filter (Daten - Filter) aktivieren und dann im Filter nach " suchen. Also im Filtersuchfeld " eingeben.
Dann findet er z.B. 500 von 3000 Zeilen, und die löschst Du alle.
Danach alles wieder kopieren in eine neue Textdatei.
Achso, verstehe ... wenn Du keine Lösung findest, ich fürchte, da wird jemand ein Skript schreiben müssen, vielleicht mit Python.
Du könntest bei Upwork ein Jobpost machen, da findest sich bestimmt ein Freelancer der für einen kleinen Preis das für Dich macht.
Sowas wie ein Script was das können sollte hab ich bereits. Ich benötige nur den korrekt escapeten? regulären Ausdruck für " und die Anweisung am Anfang einer Zeile zu suchen. Normalerweise sollte das nicht mehr als 1 Zeile Code sein... Dafür jetzt extra tagelang einen Freelancer suchen oder Geld ausgeben?
PS: Ich sehe gerade, Du willst die Zeilen ohne " am Anfang löschen.
Hier kannst Du es umgekehrt machen, also erst Filtern alle Zeilen die mit " anfangen, und die markiert Du in Grün und danach filterst Du nach allen Zeilen die nicht Grün sind, und die löschst Du.
Wieso hast Du grep, wenn Du die Sache mit Bordmitteln lösen?
In Batch gibts dafür findstr .
Allerdings spielt Batch/Cmd bezüglich DoubleQuotes (") nach eigenen Regeln. SingleQuotes(') werden als ganz normale Textzeichen verwendet und haben keinerlei Steuerfunktionen (außer bei For/f-backq"-loops, aber das ist eine Story für sich) ergo kan Dein grep nicht funktionieren. Strings mit Spaces und Sonderzeichen sind mit Doublequotes als solche zu maskieren. "DoubleQuotes" innerhalb eines durch Doublequotes maskierten Strings sind durch verdoppeln speziell zu maskieren (in einigen Ausnahmefällen kann auch durch einen Backslash\ maskiert werden, was aber vom jeweiligen aufgerufenen Programm abhängt und bei einer ungeraden Anzahl von Doublequotes den cmd-Parser aus dem Tritt bringen kann)
Beispiele:
geht schief:
Befehl "blub " irgendwas"
so wäre es richtig:
Befehl "blub "" irgendwas"
Damit findstr keinen DateiInformationen am Anfang jeder Zeile ausgibt lesen wir die Datei mit type und pipen die gelesenen Zeilen an findstr. Die cmd-Befehlszeile für das von Dir gewünschte/beschriebene Verhalten sähe dann so aus:
zeigt die Zeilen zum anschauen im Fenster an:
type "C:\Dein\pfad\test.txt"|findstr /vrc:"^"".*"
Erklärung Parameter:
- /vrc: v=Zeieln ausschleißen , r=Regex verwenden, c=literalerString (leer- und sonderzeichen sind nur text)
- "^"".*" ^zeilenanfang ""ein Doublequote .* kein oder mehr beliebige zeichen
Damit die guten Zeilen wieder in eine Datei geschrieben werden leitest Du die Ausgabe der oben ermittelten Zeilen einfach in eine Neue Datei um (kannst Du später umbenennen).
type "C:\Dein\pfad\test.txt" |findstr /vrc:"^"".*" >"neue Datei.txt"
Das war' s im einfachste Fall... und wieder ein Pferdefuß von cmd und findstr :wenn es schlecht läuft enthält sind gefundenen die Zeilen sind länger als 1023 Zeichen , dann meutert findstr und es hilft nur Powershell als bordeigenes Werkzeug:
in Powershell gibt es Mehrere Möglichkeiten:
erstmal nur als Text zum anschauen ins Fenster ausgeben:
(Select-String -Path 'test.txt' -Pattern '^"' -NotMatch) -replace '^.+:\d+:','' #nicht wirklich elegant aber schnell
Get-Content -Path 'test.txt' -Encoding UTF8 |Select-String -Pattern '^"' -NotMatch #schon besser
Get-Content -Path 'test.txt' -Encoding UTF8 |Where-Object {$_ -NotMatch '^".*'} #kommt ohne das wuchtige Select-Sering zurecht (eigentlich mein Favorit)
jetzt musst du die gefundenen Zeilen nur wieder in eine Datei bekommen.
das geht wie bei cmd mit einer Ausgabeumleitung in eine Zieldatei >''Neue_Datei.txt' :
Get-Content -Path 'test.txt' -Encoding UTF8 |Where-Object {$_ -NotMatch '^".*'} >'Neue_Datei.txt'
...oder mit Set-Content :
Get-Content -Path 'test.txt' -Encoding UTF8 |Where-Object {$_ -NotMatch '^".*'} |Set-Content -Path 'Neue_Datei.txt'
und wenn Du ganz schreibfaul bist gibts auch noch die Kurzform :
gc 'test.txt' -enc UTF8 |?{$_ -NotMatch '^".*'} |sc 'neu 3.txt'
ich würde Dir in jedem Fall die Verwendung von Powershell empfehlen, da es nicht die Einschränkunen von cmd und Findstr hat und RegEx volle Konformität zu .Net hat.
Also eine Lösung mit Findstr hatte ich auch schon probiert, erzeugt aber massig Fehler, v.a bei Zeilen, die verschiedene Sonderzeichen enthalten und im Vgl zu Grep halt irre langsam. Powershell mit Get & Set-Content funktioniert und sieht auch fast so aus wie die Lösung, die ich schon umgesetzt hatte. Das Problem mit Powershell ist halt, dass auch das zu langsam läuft (Schreibrate bei mir mit ca. 1-2MB pro Sek. + stark stromfressend) Siehst du eine Lösung wie man das beschleunigen könnte? Ich müsste es auf etwa 10MB, noch besser 20MB pro Sek bringen, damit es von der Zeit her hinhaut. Grep z.b bekommt ja solche Verarbeitungsraten locker hin....
Autsch.
Solch riesige Datenengen fallen im allgemeinen Gebrauch von Scripten nicht an, so das sich kaum jemand um Speed kümmert.
Also habe ich mal eine Datei mit 5Mio Zeilen (47MB) generiert, wovon 1Mio Zeilen mi einem " am Anfang markiert waren.
gc 'C:\test\input.txt' -enc UTF8 |?{$_ -NotMatch '^".*'} |sc 'neu.txt'}
benötigt auf meinem Rechner (@4,2GHz Singlethreaded) 147,6878904 Sekunden (ca. 3MB/s)
Ich habe alle Flasschenhälse durch konsequente Verwendung von puren .Net-Methoden eliminiert:
- In- und Output via System.IO.StreamReader & System.IO.StreamWriter
(das war schon mal eine Verdopplung der Geschwindigkeit)
- RegEx via System.Text.RegularExpressions
- Statt zeilenweise mit der Matches-Methode jeden einzelnen Treffer herauszufiltern , ersetze ich einfach via replace-Methode alle Zeilen mit " am Anfang bis inklusive Zeilenvorschub (\r?\n) durch einen Leerstring ''
$SR = New-Object System.IO.StreamReader("c:\test\input.txt")
$SW = New-Object System.IO.StreamWriter("c:\test\neu.txt",$false)
$Content = ($SR.ReadToEnd())
$SR.Close()
$MyFilteredText = ([regex]::Replace($content, '^".*(\r?\n)*','', 'Multiline,IgnoreCase'))
$SW.Write([String]$MyFilteredText)
$SW.Close()
zugegeben ich war selbst überrascht:
2,3523737 Sekunden (ca. 20MB/s) , das ist 65mal schneller als zuvor und nur ein Paar Millisekunden langsamer als C#. Die Operation noch auf mehre Threads zu verteilen bringt unter einer Dateigröße von schätzungsweise 200 MB keine Vorteile, da das Einrichten und "Einsammeln" der Threads (Powershell Runspaces) auch Zeit ca 100..200msec/Runspace benötigt
Wesentlich schneller geht es nur noch in C++
Hast du ggf. Beispiele, wo es nicht funktioniert? Ich habe keinerlei Probleme die Zeilen zu finden, die ein " am Anfang haben oder eben welche, die das nicht haben. Sprich deine zweite Lösung mit escapten " tut bei mir schon. Oder eben etwas ala ^[^\"] um die Sachen zu finden, die kein " am Anfang haben.
Also bei mir führt die Verwendung von grep '^\"' (2te Lösung?) dazu, dass mit "C:\input.csv" >> "C:\ordner\output.csv" keine Ausgabedatei (mehr) erzeugt wird, sondern der Text nur unendlich in der Konsole runterläuft. grep '^"' hingegen z.b erzeugt die Datei korrekt gibt aber alle Zeilen aus, die irgendwo in der Zeile ein " enthalten, nicht nur am Anfang.
Bei mir funktionieren beide Lösungen. Getestet auf einem Windows 10 Pro System innerhalb der Git Bash mintty 3.7.0 (MINGW64).
Ggf. liegt es an den Daten. Ist sichergestellt, dass es echt einfache Double Quotes sind? Die gibt es jeweils noch in Schräglage nach links und rechts und natürlich können zwei Single Quotes je nach Schriftart auch aussehen wie Double Quotes.
Ansonsten wäre interessant was du in Windows verwendest, um grep zu nutzen. mintty? Cygwin? Was ganz anderes?
Ebenso wäre interessant, ob es an den Daten liegt, sprich kannst du uns hier ein kleines Beispiel geben, was nicht funktioniert? Hast du das Problem nur mit explizit deiner Datei oder auch, wenn du es einfach mit etwas kleinem probierst wie:
"TEST";"TEST"
"TEST";"TEST
"TEST";TEST"
"TEST";TEST
"TEST;"TEST"
"TEST;"TEST
"TEST;TEST"
"TEST;TEST
TEST";"TEST"
TEST";"TEST
TEST";TEST"
TEST";TEST
TEST;"TEST"
TEST;"TEST
TEST;TEST"
TEST;TEST
Falls es nicht daran liegt, dass z.B. irgendein nicht Standardzeichen den Kram unterbricht und zerlegt.
Oder kurz, gib uns ein konkretes Beispiel inkl. der Art, wie du es probierst, damit man es nachstellen kann.
Grep funktioniert prima. Der RE hier besteht aus nur zwei Zeichen (^ und "). Das Problem besteht darin, diese Zeichen in CMD einzugeben. Die Regeln dazu sind gruselig. Sobald doppelte Anführungszeichen ins Spiel kommen, dupliziere ich sie und schließe das Ganze in doppelte Anführungszeichen ein. So sollte es also gehen:
grep "^""" ...
Wenn's hart auf hart kommt, kann man den RE auch in eine Datei schreiben und mit grep -f verwenden.
Tipp: Mit sed kannst Du direkt die Originaldateien ändern:
sed -i "/^""/!p" filename(s)
löscht in CMD alle Zeilen, die nicht mit " beginnen, aus jeder Eingabedatei. Auch hier kann man komplizierte Kommandos in eine Datei schreiben und mit -f verwenden.
grep "^""" scheint die Lösung zu sein (vielen Dank), naja das man am Ende 3 """ hinter dem "^ benötigt, muss man auch erst mal drauf kommen!
Excel oder irgendwas manuell kopieren ist genauso wie der Ansatz mit Powershell keine Option, da es hier um sehr große und sehr zahlreiche Dateien geht und das Hauptanliegen für meine Frage in erster Linie der Faktor Zeit ist. Es wäre also sehr gut wenn es einen funktionierenden Ansatz per Konsole (Grep oder Ähnl) gibt.