Usuń dodatkowe linie nagłówka z pliku, z wyjątkiem pierwszej linii

18

Mam plik, który wygląda jak ten przykład zabawki. Mój rzeczywisty plik ma 4 miliony wierszy, z których około 10 muszę usunąć.

ID  Data1  Data2
1    100    100
2    100    200
3    200    100
ID  Data1  Data2
4    100    100
ID  Data1  Data2
5    200    200

Chcę usunąć wiersze, które wyglądają jak nagłówek, z wyjątkiem pierwszego wiersza.

Ostateczny plik:

ID  Data1  Data2
1    100    100
2    100    200
3    200    100
4    100    100
5    200    200

W jaki sposób mogę to zrobić?

Gajusz August
źródło

Odpowiedzi:

26
header=$(head -n 1 input)
(printf "%s\n" "$header";
 grep -vFxe "$header" input
) > output
  1. pobierz wiersz nagłówka z pliku wejściowego do zmiennej
  2. wydrukuj nagłówek
  3. przetwarza plik, grepaby pominąć wiersze pasujące do nagłówka
  4. przechwyć dane wyjściowe z powyższych dwóch kroków do pliku wyjściowego
Jeff Schaller
źródło
2
a może { IFS= read -r head; printf '%s\n' "$head"; grep -vF "$head" ; } <file
iruvar
Oba dobre dodatki. Podziękowania dla don_crissti za pośrednie wskazanie, że posix niedawno usunął składnię -1 z głowy, na korzyść -n 1.
Jeff Schaller
3
@JeffSchaller, ostatnio jak 12 lat temu. I head -1był przedtem przestarzały.
Stéphane Chazelas
36

Możesz użyć

sed '2,${/ID/d;}'

Spowoduje to usunięcie wierszy o ID zaczynających się od wiersza 2.

bkmoney
źródło
3
ładny; lub ściślej mówiąc, dopasowując wzór, sed '2,${/^ID Data1 Data2$/d;}' file(oczywiście używając właściwej liczby spacji między kolumnami)
Jeff Schaller
Hm, myślałem, że możesz pominąć średnik tylko dla 1 polecenia, ale dobrze.
bkmoney
Nie z rozsądkiem sed, nie.
mikeserv
aaa i -i dla zwycięskiej edycji w miejscu.
user2066657
4
Lubsed '1!{/ID/d;}'
Stéphane Chazelas
10

Dla tych, którzy nie lubią nawiasów klamrowych

sed -e '1n' -e '/^ID/d'
  • noznacza passlinię nr1
  • d usuń wszystkie pasujące linie rozpoczynające się od ^ID
Costas
źródło
5
Można to również skrócić do sed '1n;/^ID/d'nazwy pliku. tylko sugestia
Valentin Bajrami
Zauważ, że spowoduje to również wydrukowanie takich wierszy, IDfooktóre nie są takie same jak nagłówek (w tym przypadku raczej nie będzie to miało znaczenia, ale nigdy nie wiadomo).
terdon
6

Oto zabawny. Możesz użyć sedbezpośrednio do usunięcia wszystkich kopii pierwszego wiersza i pozostawienia wszystkiego na swoim miejscu (w tym samego pierwszego wiersza).

sed '1{h;n;};G;/^\(.*\)\n\1$/d;s/\n.*$//' input

1{h;n;}umieszcza pierwszy wiersz w polu wstrzymania, drukuje go i czyta w następnym wierszu - pomijając pozostałe sedpolecenia dla pierwszego wiersza. ( Pomija1 również ten pierwszy test dla drugiej linii , ale to nie ma znaczenia, ponieważ test ten nie miałby zastosowania do drugiej linii).

G dołącza znak nowej linii, a następnie zawartość przestrzeni wstrzymania do obszaru wzoru.

/^\(.*\)\n\1$/dusuwa zawartość przestrzeni wzorów (tym samym przechodząc do następnego wiersza), jeśli część po nowej linii (tj. ta, która została dodana z przestrzeni wstrzymania) dokładnie pasuje do części przed nową linią. To tutaj wiersze, które duplikują nagłówek, zostaną usunięte.

s/\n.*$//usuwa część tekstu dodaną przez Gpolecenie, dzięki czemu drukowana jest tylko linia tekstu z pliku.

Ponieważ jednak wyrażenie regularne jest drogie, nieco szybszym podejściem byłoby użycie tego samego warunku (zanegowanie) i Pzerwanie do nowej linii, jeśli część po nowej linii (tj. To, co zostało dodane z przestrzeni wstrzymania) nie pasuje dokładnie do części przed znakiem nowej linii, a następnie bezwarunkowo usuń przestrzeń wzorców:

sed '1{h;n;};G;/^\(.*\)\n\1$/!P;d' input

Dane wyjściowe po podaniu danych wejściowych to:

ID  Data1  Data2
1    100    100
2    100    200
3    200    100
4    100    100
5    200    200
Dzika karta
źródło
Powiązane: vi.stackexchange.com/q/6269/4676
Wildcard
@don_crissti, ciekawy dodatek; dzięki! Prawdopodobnie wybrałbym dłuższy, ale równoważny sed '1{h;n;};G;/^\(.*\)\n\1$/d;P;d' input; w jakiś sposób łatwiej mi to czytać. :)
Wildcard
Powiązane również: unix.stackexchange.com/a/417736/135943
Wildcard
5

Oto kilka innych opcji, które nie wymagają wcześniejszej znajomości pierwszego wiersza:

perl -ne 'print unless $_ eq $k; $k=$_ if $.==1; 

-nFlaga mówi Perl do pętli nad jego pliku wejściowego, oszczędzając każdy wiersz jako $_. $k=$_ if $.==1;Oszczędza pierwsza linia ( $.to numer linii, więc $.==1będzie tylko prawda w 1. linii), jak $k. Te print unless $k eq $_odciski bieżącej linii, jeśli nie jest taki sam jak ten, zapisany w$k .

Alternatywnie to samo w awk:

awk '$0!=x;(NR==1){x=$0}' file 

Tutaj sprawdzamy, czy bieżący wiersz jest taki sam, jak zapisany w zmiennej x. Jeśli test ma $0!=xwartość true (jeśli bieżący wiersz $0nie jest taki sam jak x), wiersz zostanie wydrukowany, ponieważ domyślną akcją dla awk w wyrażeniach prawdziwych jest drukowanie. Pierwszy wiersz ( NR==1) jest zapisywany jako x. Ponieważ odbywa się to po sprawdzeniu, czy bieżąca linia pasuje x, zapewnia to, że pierwsza linia również zostanie wydrukowana.

terdon
źródło
Lubię nie znać idei pierwszego wiersza, ponieważ sprawia, że ​​jest to ogólny skrypt dla twojego zestawu narzędzi.
Mark Stewart
1
ta metoda awk tworzy pustą / fałszywą pozycję tablicy dla każdej linii; dla linii 4M, jeśli wszystkie różnią się (nie są jasne od Q) i są dość krótkie (wydaje się, że tak), prawdopodobnie jest to w porządku, ale jeśli jest dużo więcej lub więcej linii, może to zmiażdżyć lub umrzeć. !($0 in a)testuje bez tworzenia i unika tego, albo awk może wykonać taką samą logikę jak w przypadku perla: '$0!=x; NR==1{x=$0}'lub jeśli wiersz nagłówka może być pusty'NR==1{x=$0;print} $0!=x'
dave_thompson_085
1
@ dave_thompson_085 gdzie tworzona jest tablica na linię? Masz na myśli !a[$0]? Dlaczego miałoby to tworzyć wpis a?
terdon
1
Ponieważ tak działa awk; patrz gnu.org/software/gawk/manual/html_node/… zwłaszcza „UWAGA”.
dave_thompson_085
1
@ dave_thompson_085 cóż, niech mnie diabli! Dzięki, nie byłam tego świadoma. Naprawiono teraz.
terdon
4

AWK jest również całkiem przyzwoitym narzędziem do takich celów. Oto przykładowy kod:

$ awk 'NR == 1 {print} NR != 1 && $0!~/ID  Data1  Data2/' rmLines.txt | head -n 10                                
ID  Data1  Data2
1    100    100
     100    200
3    200    100
1    100    100
     100    200
3    200    100
1    100    100
     100    200
3    200    100

Podział :

  • NR == 1 {print} każe nam wydrukować pierwszą linię pliku tekstowego
  • NR != 1 && $0!~/ID Data1 Data2/ operator logiczny &&mówi AWK, aby wypisał wiersz, który nie jest równy 1 i nie zawiera ID Data1 Data2. Zwróć uwagę na brak{print} części; w awk, jeśli warunek testu zostanie oceniony jako prawdziwy, zakłada się, że wiersz zostanie wydrukowany.
  • | head -n 10to tylko niewielki dodatek ograniczający wyjście do tylko pierwszych 10 linii. Nie dotyczy AWKsamej części, służy wyłącznie do celów demonstracyjnych.

Jeśli chcesz tego w pliku, przekieruj dane wyjściowe polecenia, dołączając je > newFile.txtna końcu polecenia, w następujący sposób:

awk 'NR == 1 {print} NR != 1 && $0!~/ID  Data1  Data2/' rmLines.txt > newFile.txt

Jak to wytrzymuje? Właściwie całkiem dobrze:

$ time awk 'NR == 1 {print} NR != 1 && $0!~/ID  Data1  Data2/' rmLines.txt > /dev/null                            
    0m3.60s real     0m3.53s user     0m0.06s system

Dygresja

Wygenerowany plik przykładowy został wykonany z zapętleniem od jednego do miliona i wydrukowaniem pierwszych czterech linii pliku (więc 4 linie razy milion równa się 4 milionom linii), co przy okazji zajęło 0,09 sekundy.

awk 'BEGIN{ for(i=1;i<=1000000;i++) printf("ID  Data1  Data2\n1    100    100\n     100    200\n3    200    100\n");  }' > rmLines.txt
Sergiy Kolodyazhnyy
źródło
Zauważ, że spowoduje to również wydrukowanie takich wierszy, ID Data1 Data2 fooktóre nie są takie same jak nagłówek (w tym przypadku raczej nie będzie to miało znaczenia, ale nigdy nie wiadomo).
terdon
@terdon tak, dokładnie tak. OP określił jednak tylko jeden wzór, który chcą usunąć, a jego przykład wydaje się potwierdzać
Sergiy Kolodyazhnyy
3

Awk, automatycznie dostosowuje się do dowolnego nagłówka:

awk '( FNR == 1) {header=$0;print $0;}
     ( FNR > 1) && ($0 != header) { print $0;}'  file1  file2 ....

tzn. w pierwszym wierszu pobierz nagłówek i wydrukuj go, a następnie wydrukuj kolejny wiersz RÓŻNY z tego nagłówka.

FNR = liczba rekordów w bieżącym pliku, dzięki czemu możesz mieć wiele plików i tak samo zrobi w każdym z nich.

Olivier Dulac
źródło
2

Dla kompletności rozwiązanie IMO w Perlu jest nieco bardziej eleganckie niż @terdon:

perl -i -p -e 's/^ID.*$//s if $. > 1' file
KWubbufetowicz
źródło
1
Ach, ale moim celem było uniknięcie konieczności określenia wzoru i przeczytania go z pierwszego wiersza. Twoje podejście po prostu usunie każdą linię, która zaczyna się od ID. Nie masz gwarancji, że nie spowoduje to usunięcia wierszy, które powinny zostać zachowane. Ponieważ wychowałeś elegancję, nie gma sensu, jeśli używasz ^i $. W rzeczywistości wszystkie twoje opcje m///są tutaj bezużyteczne, z wyjątkiem s; aktywują funkcje, których nie używasz. Więc to $, s/^ID.*//sby zrobić to samo.
terdon
@terdon, w porządku. Twój jest o wiele bardziej uniwersalny!
KWubbufetowicz
2

Po prostu odsuńmy się nieco od pytania ... wygląda na to, że twój wkład sam w sobie jest wynikiem połączenia kilku plików TSV razem. Jeśli możesz wykonać kopię zapasową kroku w procesie przetwarzania (jeśli jesteś jej właścicielem lub możesz porozmawiać z ludźmi, którzy to robią), możesz w pierwszej kolejności użyć narzędzia rozpoznającego nagłówek, aby połączyć dane, a tym samym usunąć problem z koniecznością usuń dodatkowe linie nagłówka.

Na przykład za pomocą Millera :

$ cat f1.tsv
ID  Data1 Data2
1 100 100
2 100 200
3 200 100
$ cat f2.tsv
ID  Data1 Data2
4 100 100
$ cat f3.tsv
ID  Data1 Data2
5 200 200

$ cat f1.tsv f2.tsv  f3.tsv
ID  Data1 Data2
1 100 100
2 100 200
3 200 100
ID  Data1 Data2
4 100 100
ID  Data1 Data2
5 200 200

$ mlr --tsvlite cat f1.tsv f2.tsv  f3.tsv
ID  Data1 Data2
1 100 100
2 100 200
3 200 100
4 100 100
5 200 200
John Kerl
źródło
1
Dziękujemy za dodanie tego smakołyka. Będzie to niezwykle przydatne w przyszłości, ponieważ większość moich potoków wymaga łączenia i scalania plików z poszczególnych próbek.
Gajusz August