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ć?

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

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.


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

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


@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


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.


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

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.


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

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

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.