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ć?
text-processing
Gajusz August
źródło
źródło
{ IFS= read -r head; printf '%s\n' "$head"; grep -vF "$head" ; } <file
head -1
był przedtem przestarzały.Możesz użyć
Spowoduje to usunięcie wierszy o ID zaczynających się od wiersza 2.
źródło
sed '2,${/^ID Data1 Data2$/d;}' file
(oczywiście używając właściwej liczby spacji między kolumnami)sed
, nie.sed '1!{/ID/d;}'
Dla tych, którzy nie lubią nawiasów klamrowych
n
oznaczapass
linię nr1
d
usuń wszystkie pasujące linie rozpoczynające się od^ID
źródło
sed '1n;/^ID/d'
nazwy pliku. tylko sugestiaIDfoo
które nie są takie same jak nagłówek (w tym przypadku raczej nie będzie to miało znaczenia, ale nigdy nie wiadomo).Oto zabawny. Możesz użyć
sed
bezpośrednio do usunięcia wszystkich kopii pierwszego wiersza i pozostawienia wszystkiego na swoim miejscu (w tym samego pierwszego wiersza).1{h;n;}
umieszcza pierwszy wiersz w polu wstrzymania, drukuje go i czyta w następnym wierszu - pomijając pozostałesed
polecenia 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$/d
usuwa 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ą przezG
polecenie, 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
P
zerwanie 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:Dane wyjściowe po podaniu danych wejściowych to:
źródło
sed '1{h;n;};G;/^\(.*\)\n\1$/d;P;d' input
; w jakiś sposób łatwiej mi to czytać. :)Oto kilka innych opcji, które nie wymagają wcześniejszej znajomości pierwszego wiersza:
-n
Flaga 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$.==1
będzie tylko prawda w 1. linii), jak$k
. Teprint unless $k eq $_
odciski bieżącej linii, jeśli nie jest taki sam jak ten, zapisany w$k
.Alternatywnie to samo w
awk
:Tutaj sprawdzamy, czy bieżący wiersz jest taki sam, jak zapisany w zmiennej
x
. Jeśli test ma$0!=x
wartość true (jeśli bieżący wiersz$0
nie jest taki sam jakx
), wiersz zostanie wydrukowany, ponieważ domyślną akcją dla awk w wyrażeniach prawdziwych jest drukowanie. Pierwszy wiersz (NR==1
) jest zapisywany jakox
. Ponieważ odbywa się to po sprawdzeniu, czy bieżąca linia pasujex
, zapewnia to, że pierwsza linia również zostanie wydrukowana.źródło
!($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'
!a[$0]
? Dlaczego miałoby to tworzyć wpisa
?AWK jest również całkiem przyzwoitym narzędziem do takich celów. Oto przykładowy kod:
Podział :
NR == 1 {print}
każe nam wydrukować pierwszą linię pliku tekstowegoNR != 1 && $0!~/ID Data1 Data2/
operator logiczny&&
mówi AWK, aby wypisał wiersz, który nie jest równy 1 i nie zawieraID 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 10
to tylko niewielki dodatek ograniczający wyjście do tylko pierwszych 10 linii. Nie dotyczyAWK
samej 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.txt
na końcu polecenia, w następujący sposób:Jak to wytrzymuje? Właściwie całkiem dobrze:
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.
źródło
ID Data1 Data2 foo
które nie są takie same jak nagłówek (w tym przypadku raczej nie będzie to miało znaczenia, ale nigdy nie wiadomo).Awk, automatycznie dostosowuje się do dowolnego nagłówka:
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.
źródło
Dla kompletności rozwiązanie IMO w Perlu jest nieco bardziej eleganckie niż @terdon:
źródło
ID
. Nie masz gwarancji, że nie spowoduje to usunięcia wierszy, które powinny zostać zachowane. Ponieważ wychowałeś elegancję, nieg
ma sensu, jeśli używasz^
i$
. W rzeczywistości wszystkie twoje opcjem///
są tutaj bezużyteczne, z wyjątkiems
; aktywują funkcje, których nie używasz. Więc to$
,s/^ID.*//s
by zrobić to samo.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 :
źródło