Wie entferne ich bestimmte Wörter aus Zeilen einer Textdatei?

13

meine textdatei sieht so aus:

Liquid penetration 95% mass (m) = 0.000205348
Liquid penetration 95% mass (m) = 0.000265725
Liquid penetration 95% mass (m) = 0.000322823
Liquid penetration 95% mass (m) = 0.000376445
Liquid penetration 95% mass (m) = 0.000425341

Jetzt möchte ich Liquid penetration 95% mass (m)aus meinen Zeilen löschen , um nur die Werte zu erhalten. Wie soll ich das machen

OE
quelle
3
einfachgrep -o '[^[:space:]]\+$' file
Avinash Raj
@AvinashRaj: Im Moment erhält diese Lösung die 'Kitt-Medaille' :)
pa4080
2
@ pa4080 Zumindest für die von mir getestete Eingabe (10 Millionen Zeilen) kann der allgemeine Ansatz von Avinash Raj mithilfe von PCRE um eine Größenordnung beschleunigt werden. (Ich könnte bestätigen, dass die Engine und nicht das Pattern dafür verantwortlich ist, wie GNU grep \S+$entweder mit -Eoder akzeptiert -P.) Diese Art von Lösung ist also nicht von Natur aus langsam. Aber ich komme immer noch nicht an die cutMethode von αғsнιη heran , die auch Ihren Benchmark gewonnen hat .
Eliah Kagan

Antworten:

22

Wenn es nur ein =Zeichen gibt, können Sie alles vorher und einschließlich =so löschen :

$ sed -r 's/.* = (.*)/\1/' file
0.000205348
0.000265725
0.000322823
0.000376445
0.000425341

Wenn Sie die Originaldatei ändern möchten, verwenden Sie die -iOption nach dem Testen:

sed -ri 's/.* = (.*)/\1/' file

Anmerkungen

  • -rbenutze ERE, damit wir nicht entkommen müssen (und)
  • s/old/newersetzen oldmitnew
  • .* Beliebig viele beliebige Zeichen
  • (things)sparen thingsspäter mit Rückreferenzierung \1, \2usw.
Zanna
quelle
Danke, es hat funktioniert. Ich habe diesen Befehl verwendet, um die vorhandene Datei zu überschreiben: sed -i -r 's /.*= (. *) / \ 1 /' time.txt Können Sie erklären, wie es funktioniert?
OE
Warum nicht den Rückbezug vermeiden? s/^.*= //würde genauso gut funktionieren, da der richtige Wert am Ende der Zeile steht.
Jpaugh
@jpaugh Nun, zum Teil, weil es zu spät ist, meine Antwort zu ändern, die die erste war, die veröffentlicht wurde - andere haben bereits die von Ihnen erwähnte Lösung und andere effizientere Möglichkeiten für diesen Fall angegeben :) Aber vielleicht hat das Zeigen, wie man \1usw. verwendet, einen gewissen Wert für Leute, die landen auf dieser Frage bei der Suche, die nicht so ein einfaches Problem haben
Zanna
@Zanna Es ist zumindest allgemeiner.
Jpaugh
21

Dies ist ein Job für awk; Angenommen, die Werte kommen nur im letzten Feld vor (gemäß Ihrem Beispiel):

awk '{print $NF}' file.txt
  • NFist eine awkVariable, die auf die Anzahl der Felder in einem Datensatz (Zeile) erweitert wird und daher $NF(beachten Sie, dass $vorne) den Wert des letzten Felds enthält.

Beispiel:

% cat temp.txt 
Liquid penetration 95% mass (m) = 0.000205348
Liquid penetration 95% mass (m) = 0.000265725
Liquid penetration 95% mass (m) = 0.000322823
Liquid penetration 95% mass (m) = 0.000376445
Liquid penetration 95% mass (m) = 0.000425341

% awk '{print $NF}' temp.txt
0.000205348
0.000265725
0.000322823
0.000376445
0.000425341
heemayl
quelle
13

Ich habe mich entschlossen, die verschiedenen hier aufgeführten Lösungen zu vergleichen. Zu diesem Zweck habe ich eine große Datei erstellt, die auf dem vom OP bereitgestellten Inhalt basiert:

  1. Ich habe eine einfache Datei mit dem Namen erstellt input.file:

    $ cat input.file
    Liquid penetration 95% mass (m) = 0.000205348
    Liquid penetration 95% mass (m) = 0.000265725
    Liquid penetration 95% mass (m) = 0.000322823
    Liquid penetration 95% mass (m) = 0.000376445
    Liquid penetration 95% mass (m) = 0.000425341
    
  2. Dann habe ich diese Schleife ausgeführt:

    for i in {1..100}; do cat input.file | tee -a input.file; done
    
  3. Terminalfenster wurde blockiert. Ich habe killall teevon einem anderen Terminal ausgeführt. Dann überprüfte ich den Inhalt der Datei mit den Befehlen: less input.fileund cat input.file. Es sah gut aus, bis auf die letzte Zeile. Also habe ich die letzte Zeile entfernt und eine Sicherungskopie erstellt: cp input.file{,.copy}(aufgrund der Befehle, die die Option inplace verwenden ).

  4. Die endgültige Anzahl der Zeilen in der Datei input.filebeträgt 2 192 473 . Ich habe diese Nummer durch den Befehl erhalten wc:

    $ cat input.file | wc -l
    2192473
    

Hier ist das Ergebnis des Vergleichs:

  • grep -o '[^[:space:]]\+$'

    $ time grep -o '[^ [: space:]] \ + $' Eingabedatei> Ausgabedatei
    
    echte 0m58.539s
    Benutzer 0m58.416s
    sys 0m0.108s
    
  • sed -ri 's/.* = (.*)/\1/'

    $ time sed -ri 's /.* = (. *) / \ 1 /' Eingabedatei
    
    echte 0m26.936s
    Benutzer 0m22.836s
    sys 0m4.092s
    

    Alternativ ist der Befehl schneller, wenn wir die Ausgabe in eine neue Datei umleiten:

    $ time sed -r 's /.* = (. *) / \ 1 /' input.file> output.file
    
    echte 0m19.734s
    Benutzer 0m19.672s
    sys 0m0.056s
    
  • gawk '{gsub(".*= ", "");print}'

    $ time gawk '{gsub (". * =", ""); print}' input.file> output.file
    
    echte 0m5.644s
    Benutzer 0m5.568s
    sys 0m0.072s
    
  • rev | cut -d' ' -f1 | rev

    $ time rev input.file | schneide -d '' -f1 | rev> output.file
    
    echte 0m3.703s
    Benutzer 0m2.108s
    sys 0m4.916s
    
  • grep -oP '.*= \K.*'

    $ time grep -oP '. * = \ K. *' input.file> output.file
    
    echte 0m3.328s
    Benutzer 0m3.252s
    sys 0m0.072s
    
  • sed 's/.*= //' (bzw. die -iOption macht den Befehl einige Male langsamer)

    $ time sed 's /.*= //' input.file> output.file
    
    echte 0m3.310s
    Benutzer 0m3.212s
    sys 0m0.092s
    
  • perl -pe 's/.*= //' (Die -iOption erzeugt hier keinen großen Unterschied in der Produktivität.)

    $ time perl -i.bak -pe 's /.*= //' Eingabedatei
    
    echte 0m3.187s
    Benutzer 0m3.128s
    sys 0m0.056s
    
    $ time perl -pe 's /.*= //' input.file> output.file
    
    echte 0m3.138s
    Benutzer 0m3.036s
    sys 0m0.100s
    
  • awk '{print $NF}'

    $ time awk '{print $ NF}' Eingabedatei> Ausgabedatei
    
    echte 0m1.251s
    Benutzer 0m1.164s
    sys 0m0.084s
    
  • cut -c 35-

    $ time cut -c 35- input.file> output.file
    
    echte 0m0.352s
    Benutzer 0m0.284s
    sys 0m0.064s
    
  • cut -d= -f2

    $ time cut -d = -f2 Eingabedatei> Ausgabedatei
    
    echte 0m0.328s
    Benutzer 0m0.260s
    sys 0m0.064s
    

Die Quelle der Idee.

pa4080
quelle
2
So gewinnt meine cut -d= -f2Lösung . haha
αғsнιη
Können Sie weitere Informationen darüber geben, wie Sie diese Datei erstellt haben? Wie wc -lwerden außerdem drei Zahlen ausgegeben? Wenn keine anderen Optionen übergeben werden, sollte die -lOption alles außer der Zeilenanzahl unterdrücken.
Eliah Kagan
@EliahKagan, fertig. Ich habe die Antwort aktualisiert.
pa4080
Ah, ich verstehe - die Leerzeichen waren Zifferngruppentrennzeichen. (Hatte wcdiese Leerzeichen tatsächlich angezeigt? Gibt es Gebietsschemaeinstellungen, für die dies möglich ist?) Vielen Dank für das Update!
Eliah Kagan
@EliahKagan: Endlich habe ich deine Fragen noch wceinmal gelesen . Ich weiß nicht, wo mein Verstand heute früh war, aber ich konnte sie wirklich nicht verstehen. In der Tat waren die Leerzeichen Zifferngruppentrennzeichen und wcfügen sie nicht hinzu :)
pa4080
12

Mit grepund der -Pfür die mit PCRE(Interpretieren des Musters als P erl- C ompatibel R egular E xpression) und die -oallein abgestimmt Muster zu drucken. Die \KBenachrichtigung ignoriert den übereinstimmenden Teil, der vor sich geht.

$ grep -oP '.*= \K.*' infile
0.000205348
0.000265725
0.000322823
0.000376445
0.000425341

Oder Sie könnten cutstattdessen den Befehl verwenden.

cut -d= -f2 infile
αғsнιη
quelle
2
Neben dem schnellsten alle Methoden zum Laufen in getestet pa4080 Benchmark , das cutVerfahren in dieser Antwort war auch der klare Sieger in einem kleineren Maßstab Ich lief die getestet weniger Methoden , sondern verwenden eine größere Eingabedatei. Es war weit über zehnmal schneller als die schnelle Variante der Methode, die ich persönlich mag (und bei der es hauptsächlich um meine Antwort geht).
Eliah Kagan
11

Da das Zeilenpräfix immer dieselbe Länge hat (34 Zeichen), können Sie Folgendes verwenden cut:

cut -c 35- < input.txt > output.txt
David Foerster
quelle
6

Kehren Sie den Inhalt der Datei mit um rev, leiten Sie die Ausgabe cutmit Leerzeichen als Trennzeichen und 1 als Zielfeld weiter und kehren Sie sie dann erneut um, um die ursprüngliche Nummer zu erhalten:

$ rev your_file | cut -d' ' -f1 | rev
0.000205348
0.000265725
0.000322823
0.000376445
0.000425341
f1nan
quelle
5

Dies ist einfach, kurz und leicht zu schreiben, zu verstehen und zu überprüfen, und ich persönlich mag es:

grep -oE '\S+$' file

grepWenn in Ubuntu mit -Eoder aufgerufen wird -P, bedeutet die Kurzschreibweise \s ein Leerzeichen (in der Praxis normalerweise ein Leerzeichen oder ein Tabulator) und \Setwas anderes. Mit dem Quantor+ und den End-of-Line - Anker$ , das Muster für \S+$ein oder mehr Nicht-Leerzeichen am Ende einer Zeile . Sie können -Panstelle von verwenden -E; Die Bedeutung ist in diesem Fall gleich, es wird jedoch eine andere reguläre Ausdrucks-Engine verwendet, sodass sie möglicherweise unterschiedliche Leistungsmerkmale aufweisen .

Dies entspricht der kommentierten Lösung von Avinash Raj (nur mit einer einfacheren, kompakteren Syntax):

grep -o '[^[:space:]]\+$' file

Diese Ansätze funktionieren nicht, wenn hinter der Zahl ein Leerzeichen stehen könnte . Sie können geändert werden, aber ich sehe keinen Grund, hier darauf einzugehen. Obwohl es manchmal lehrreich ist, eine Lösung zu verallgemeinern, um in mehreren Fällen zu arbeiten, ist es nicht praktisch, dies fast so oft zu tun, wie die Leute annehmen, weil man normalerweise nicht weiß, auf welche von vielen verschiedenen inkompatiblen Arten das Problem letztendlich benötigt wird verallgemeinert werden.


Leistung ist manchmal ein wichtiger Gesichtspunkt. Diese Frage besagt nicht, dass die Eingabe sehr umfangreich ist, und es ist wahrscheinlich, dass jede hier veröffentlichte Methode schnell genug ist. Für den Fall, dass Geschwindigkeit gewünscht wird, finden Sie hier einen kleinen Benchmark für eine 10-Millionen-Zeilen-Eingabedatei:

$ perl -e 'print((<>) x 2000000)' file > bigfile
$ du -sh bigfile
439M    bigfile
$ wc -l bigfile
10000000 bigfile
$ TIMEFORMAT=%R
$ time grep -o '[^[:space:]]\+$' bigfile > bigfile.out
819.565
$ time grep -oE '\S+$' bigfile > bigfile.out
816.910
$ time grep -oP '\S+$' bigfile > bigfile.out
67.465
$ time cut -d= -f2 bigfile > bigfile.out
3.902
$ time grep -o '[^[:space:]]\+$' bigfile > bigfile.out
815.183
$ time grep -oE '\S+$' bigfile > bigfile.out
824.546
$ time grep -oP '\S+$' bigfile > bigfile.out
68.692
$ time cut -d= -f2 bigfile > bigfile.out
4.135

Ich habe es zweimal ausgeführt, für den Fall, dass die Reihenfolge wichtig ist (wie es manchmal für E / A-schwere Aufgaben der Fall ist), und weil ich keine Maschine zur Verfügung hatte, die keine anderen Aufgaben im Hintergrund ausführte, die die Ergebnisse verzerren könnten. Aus diesen Ergebnissen schließe ich zumindest vorläufig und für Eingabedateien der von mir verwendeten Größe Folgendes:

  • Beeindruckend! Passing -P(zu Verwendung PCRE ) statt -G(die Standardeinstellung , wenn kein Dialekt angegeben wird) oder -Eaus grepschneller um mehr als eine Größenordnung. Für große Dateien ist es möglicherweise besser, diesen Befehl zu verwenden als den oben gezeigten:

    grep -oP '\S+$' file
  • BEEINDRUCKEND!! Das cutVerfahren in αғsнιη Antwort , ist über eine Größenordnung schneller als auch die schnellere Version meiner Art und Weise! Es war auch der Gewinner im Benchmark von pa4080 , der mehr Methoden als diese, aber mit geringerem Input abdeckte - und aus diesem Grund habe ich es von allen anderen Methoden ausgewählt, um es in meinen Test aufzunehmen. Wenn Leistung wichtig ist oder Dateien sehr groß sind, sollte meiner Meinung nach die Methode von αιsнιη verwendet werden.cut -d= -f2 filecut

    Dies dient auch als Erinnerung daran, dass die einfachen cutund pasteHilfsprogramme nicht vergessen werden sollten und gegebenenfalls bevorzugt werden sollten, obwohl es komplexere Tools wie grepdiese gibt, die häufig als First-Line-Lösungen angeboten werden (und an die ich persönlich gewöhnter bin) verwenden).

Eliah Kagan
quelle
4

perl- s ubstitute das Muster /.*= /mit leeren String //:

perl -pe 's/.*= //' input.file > output.file
perl -i.bak -pe 's/.*= //' input.file
  • Von perl --help:

    -e program        one line of program (several -e's allowed, omit programfile)
    -p                assume loop like -n but print line also, like sed
    -i[extension]     edit <> files in place (makes backup if extension supplied)
    

sed - Ersetze das Muster durch eine leere Zeichenkette:

sed 's/.*= //' input.file > output.file

oder (aber langsamer als oben) :

sed -i.bak 's/.*= //' input.file
  • Ich erwähne diesen Ansatz, weil er einige Male schneller ist als die in Zannas Antwort .

gawk - Ersetzen Sie das Muster ".*= " durch eine leere Zeichenkette "":

gawk '{gsub(".*= ", "");print}' input.file > output.file
  • Von man gawk:

    gsub(r, s [, t]) For each substring matching the regular expression r in the string t,
                     substitute the string s, and return the number of substitutions. 
                     If t is not supplied, use $0...
    
pa4080
quelle