Jak usunąć określone słowa z wierszy pliku tekstowego?

13

mój plik tekstowy wygląda następująco:

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

teraz chcę usunąć Liquid penetration 95% mass (m)z moich linii, aby uzyskać tylko wartości. Jak mam to zrobić?

OE
źródło
3
po prostugrep -o '[^[:space:]]\+$' file
Avinash Raj
@AvinashRaj: Do chwili obecnej to rozwiązanie otrzymuje „kit kit” :)
pa4080
2
@ pa4080 Przynajmniej dla wejścia, które testowałem (10 mln linii), ogólne podejście Avinash Raj można przyspieszyć o rząd wielkości, używając PCRE. (Mógłbym potwierdzić, że silnik, a nie wzorzec, jest odpowiedzialny, jak GNU grep akceptuje \S+$albo -Ealbo -P.) Tak więc tego rodzaju rozwiązanie nie jest z natury powolne. Ale nadal nie mogę go zbliżyć do metody αғsнιηcut , która również wygrała Twój test porównawczy .
Eliah Kagan

Odpowiedzi:

22

Jeśli jest tylko jeden =znak, możesz usunąć wszystko wcześniej i w =ten sposób:

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

Jeśli chcesz zmienić oryginalny plik, użyj -iopcji po przetestowaniu:

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

Notatki

  • -rużyj ERE, abyśmy nie musieli uciekać (i)
  • s/old/newwymienić oldznew
  • .* dowolna liczba dowolnych znaków
  • (things)zapisz thingssię wsteczne później \1, \2itp
Zanna
źródło
Dzięki, działało. Użyłem tego polecenia, aby zastąpić istniejący plik: sed -i -r 's /.*= (. *) / \ 1 /' time.txt Czy możesz wyjaśnić, jak to działa?
OE
Dlaczego nie uniknąć odwołania wstecznego? s/^.*= //działałoby równie dobrze, ponieważ poprawna wartość znajduje się na końcu wiersza.
jpaugh
@jpaugh Cóż, częściowo dlatego, że jest za późno, aby zmienić moją odpowiedź, która była pierwszą opublikowaną - inni podali już wspomniane rozwiązanie i inne bardziej wydajne sposoby na ten przypadek :) Ale może pokazanie, jak używać \1etc ma wartość dla ludzi, którzy wyląduj na tym pytaniu podczas wyszukiwania, którzy nie mają tak prostego problemu
Zanna
@Zanna To jest przynajmniej bardziej ogólne.
jpaugh
21

To jest praca dla awk; zakładając, że wartości występują tylko w ostatnim polu (jak w twoim przykładzie):

awk '{print $NF}' file.txt
  • NFjest awkzmienną, rozwija się do liczby pól w rekordzie (linii), dlatego $NF(zwróć uwagę na $początek) zawiera wartość ostatniego pola.

Przykład:

% 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
źródło
13

Postanowiłem porównać różne rozwiązania wymienione tutaj. W tym celu stworzyłem duży plik, oparty na treści dostarczonej przez PO:

  1. Utworzyłem prosty plik o nazwie 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. Następnie wykonałem tę pętlę:

    for i in {1..100}; do cat input.file | tee -a input.file; done
    
  3. Okno terminala zostało zablokowane. Wykonałem killall teez innego terminala. Następnie sprawdziłem zawartość pliku za pomocą poleceń: less input.filei cat input.file. Wyglądało dobrze, z wyjątkiem ostatniej linii. Więc usunąłem ostatni wiersz i utworzyłem kopię zapasową: cp input.file{,.copy}(z powodu poleceń, które używają opcji inplace ).

  4. Ostateczna liczba wierszy w pliku input.filewynosi 2 192 473 . Mam ten numer przez polecenie wc:

    $ cat input.file | wc -l
    2192473
    

Oto wynik porównania:

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

    $ time grep -o '[^ [: space:]] \ + $' input.file> output.file
    
    prawdziwe 0m58,539s
    użytkownik 0m58.416s
    sys 0m0.108s
    
  • sed -ri 's/.* = (.*)/\1/'

    $ time sed -ri 's /.* = (. *) / \ 1 /' input.file
    
    prawdziwe 0m26,936s
    użytkownik 0m22,836s
    sys 0m4.092s
    

    Alternatywnie, jeśli przekierujemy dane wyjściowe do nowego pliku, polecenie jest szybsze:

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

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

    $ time rev input.file | cut -d '' -f1 | rev> plik wyjściowy
    
    prawdziwe 0m3.703s
    użytkownik 0m2.108s
    sys 0m4.916s
    
  • grep -oP '.*= \K.*'

    $ time grep -oP '. * = \ K. *' input.file> output.file
    
    prawdziwe 0m3.328s
    użytkownik 0m3.252s
    sys 0m0,072s
    
  • sed 's/.*= //' (odpowiednio -iopcja powoduje, że polecenie jest kilka razy wolniejsze)

    $ time sed 's /.*= //' input.file> output.file
    
    prawdziwe 0m3.310s
    użytkownik 0m3.212s
    sys 0m0,092s
    
  • perl -pe 's/.*= //' ( -iopcja nie powoduje tutaj dużej różnicy w wydajności)

    $ time perl -i.bak -pe 's /.*= //' plik wejściowy
    
    prawdziwe 0m3,187
    użytkownik 0m3.128s
    sys 0m0,056s
    
    $ time perl -pe 's /.*= //' input.file> output.file
    
    prawdziwe 0m3.138s
    użytkownik 0m3.036s
    sys 0m0.100s
    
  • awk '{print $NF}'

    $ time awk '{print $ NF}' input.file> output.file
    
    prawdziwe 0m1,251s
    użytkownik 0m1,164s
    sys 0m0.084s
    
  • cut -c 35-

    $ time cut -c 35- plik.wejściowy> plik.wyjściowy
    
    prawdziwe 0m0,352s
    użytkownik 0m0,284s
    sys 0m0.064s
    
  • cut -d= -f2

    $ time cut -d = -f2 plik wejściowy> plik wyjściowy
    
    prawdziwe 0m0,328s
    użytkownik 0m0,260s
    sys 0m0.064s
    

Źródło pomysłu.

pa4080
źródło
2
więc moje cut -d= -f2rozwiązanie wygrywa. haha
αғsнιη
Czy możesz podać więcej informacji o tym, jak utworzyłeś ten plik? Ponadto, w jaki sposób wc -lwypisuje trzy liczby? Gdy nie zostaną przekazane żadne inne opcje, -lopcja powinna pomijać wszystko oprócz liczby linii.
Eliah Kagan
@EliahKagan, gotowe. Zaktualizowałem odpowiedź.
pa4080
Ach, rozumiem - spacje były cyfrowymi separatorami grup. (Czy wcrzeczywiście wyświetlał te spacje? Czy istnieją ustawienia regionalne, dla których to zrobi?) Dziękujemy za aktualizację!
Eliah Kagan
@EliahKagan: W końcu przeczytałem twoje pytania o wcjeszcze raz. Nie wiem, gdzie dzisiaj był mój rozum, ale naprawdę nie mogłem ich zrozumieć. Rzeczywiście spacje były cyfrowymi separatorami grup i wcnie dodają ich :)
pa4080 24.10.17
12

O grepa -Po o PCRE(interpretować szablon jako P erl- C ompatible R egular E Xpression) i -odrukowanie dopasowane samego wzoru. \KZawiadomić zignoruje dopasowana część przyjść przed siebie.

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

Lub cutzamiast tego możesz użyć polecenia.

cut -d= -f2 infile
αғsнιη
źródło
2
Oprócz uruchamiania najszybszy z wszystkich testowanych metod odniesienia pa4080 za , metoda ta odpowiedź była również wyraźnym zwycięzcą w mniejszym odniesienia wpadłem że sprawdzone metody mniej jednak stosować większy plik wejściowy. To było ponad dziesięć razy szybsze niż szybka odmiana metody, którą osobiście lubię (i że moja odpowiedź dotyczy głównie). cut
Eliah Kagan
11

Ponieważ prefiks linii ma zawsze tę samą długość (34 znaki), możesz użyć cut:

cut -c 35- < input.txt > output.txt
David Foerster
źródło
6

Odwróć zawartość pliku za pomocą rev, potokuj wyjście za cutpomocą spacji jako separatora i 1 jako pola docelowego, a następnie odwróć go ponownie, aby uzyskać oryginalny numer:

$ rev your_file | cut -d' ' -f1 | rev
0.000205348
0.000265725
0.000322823
0.000376445
0.000425341
f1nan
źródło
5

To jest proste, krótkie i łatwe do napisania, zrozumienia i sprawdzenia, a ja osobiście to lubię:

grep -oE '\S+$' file

grepw Ubuntu , gdy jest wywoływany za pomocą -Elub -P, oznacza skrót, \s że oznacza spację (w praktyce zwykle spację lub tabulator) i \Soznacza wszystko, co nie jest jednym. Używając kwantyfikatora+ i kotwicy końca linii$ , wzór \S+$dopasowuje jeden lub więcej niepustych znaków na końcu linii . Możesz użyć -Pzamiast -E; znaczenie w tym przypadku jest takie samo, ale używany jest inny silnik wyrażeń regularnych , więc mogą mieć różne charakterystyki wydajności .

Jest to równoważne z komentarzem rozwiązania Avinash Raj (tylko z łatwiejszą, bardziej zwartą składnią):

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

Te podejścia nie będą działać, jeśli po numerze mogą występować białe spacje . Można je modyfikować, ale robią to, ale nie widzę sensu w tym wchodzić. Chociaż czasem pouczające jest uogólnienie rozwiązania do pracy w większej liczbie przypadków, nie jest to praktyczne tak często, jak ludzie się przypuszczają, ponieważ zwykle nie ma sposobu, aby dowiedzieć się, na który z wielu różnych niekompatybilnych sposobów problem może ostatecznie wymagać uogólniać się.


Wydajność jest czasem ważnym czynnikiem. To pytanie nie określa, że ​​dane wejściowe są bardzo duże i prawdopodobne jest, że każda opublikowana tutaj metoda jest wystarczająco szybka. Jednak w przypadku, gdy pożądana jest prędkość, oto mały punkt odniesienia dla pliku wejściowego o wartości dziesięciu milionów wierszy:

$ 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

Uruchomiłem go dwa razy, na wypadek, gdyby kolejność miała znaczenie (jak to czasem bywa w przypadku zadań wymagających dużej ilości operacji we / wy) i ponieważ nie miałem dostępnej maszyny, która nie wykonywałaby w tle innych rzeczy, które mogłyby wypaczać wyniki. Na podstawie tych wyników wyciągam następujące wnioski, przynajmniej tymczasowo i dla plików wejściowych o rozmiarze, którego użyłem:

  • Łał! Przechodząc -P(by użyć PCRE ) zamiast -G(domyślnie, gdy nie jest określony dialekt) lub -Ewykonane grepszybciej o ponad rząd wielkości. Dlatego w przypadku dużych plików lepiej użyć tego polecenia niż powyższe:

    grep -oP '\S+$' file
  • ŁAŁ!! cutMetoda w odpowiedzi αғsнιη za , jest ponad rząd wielkości szybciej niż nawet szybszą wersją mojej drodze! Był również zwycięzcą testu porównawczego pa4080 , który obejmował więcej metod niż to, ale przy mniejszym wkładzie - i dlatego wybrałem go spośród wszystkich innych metod do włączenia do mojego testu. Jeśli wydajność jest ważna lub pliki są ogromne, uważam, że należy zastosować metodę αғsнιη .cut -d= -f2 filecut

    Służy to również przypomnieniu, że nie należy zapominać o prostocie cuti pastenarzędziach , i być może należy ją preferować, gdy ma to zastosowanie, mimo że istnieją bardziej wyrafinowane narzędzia, takie jak te, grepktóre są często oferowane jako rozwiązania pierwszej linii (i że jestem osobiście bardziej przyzwyczajony do korzystania).

Eliah Kagan
źródło
4

perl- s tworzy wzór /.*= /z pustym łańcuchem //:

perl -pe 's/.*= //' input.file > output.file
perl -i.bak -pe 's/.*= //' input.file
  • Od 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 - zamień wzór na pusty ciąg:

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

lub (ale wolniej niż powyżej) :

sed -i.bak 's/.*= //' input.file
  • Wspominam o tym podejściu, ponieważ jest ono kilka razy szybsze niż w odpowiedzi Zanny .

gawk- zamień wzór na ".*= "pusty ciąg "":

gawk '{gsub(".*= ", "");print}' input.file > output.file
  • Od 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
źródło