Zachowaj tylko wiersze zawierające dokładną liczbę ograniczników

9

Mam ogromny plik csv z 10 polami oddzielonymi przecinkami. Niestety niektóre wiersze są zniekształcone i nie zawierają dokładnie 10 przecinków (co powoduje pewne problemy, gdy chcę odczytać plik do R). Jak mogę odfiltrować tylko wiersze zawierające dokładnie 10 przecinków?

Miroslav Sabo
źródło
1
twoje pytanie i powiązane pytanie nie są tym samym pytaniem. pytasz, jak obsługiwać linie z nie więcej niż określoną liczbą dopasowań, podczas gdy to pytanie wymaga tylko minimalnej liczby dopasowań. w rzeczywistości odpowiedź na pytanie jest łatwiejsza - nie wymaga pełnego skanowania linii lub (przynajmniej tak jak sedtutaj) tylko jednego pasowania więcej niż jest to poszukiwane, choć pytanie to robi. Nie powinieneś tego zamykać.
mikeserv
1
właściwie, patrząc bliżej, pytający nie chce nie więcej lub mniej niż zapałek. to pytanie wymaga nowego tytułu. ale grepodpowiedź nie jest do zaakceptowania dla obu pytań ...
mikeserv

Odpowiedzi:

21

Kolejny POSIX:

awk -F , 'NF == 11' <file

Jeśli linia ma 10 przecinków, w tej linii będzie 11 pól. Więc po prostu uczynić awkwykorzystania ,jako separatora pól. Jeśli liczba pól wynosi 11, warunek NF == 11jest spełniony, awka następnie wykonuje domyślną akcję print $0.

Cuonglm
źródło
5
To właściwie pierwsza rzecz, jaka przyszła mi do głowy w tej kwestii. Myślałem, że to przesada, ale patrząc na kod ... na pewno jest wyraźniej. Z korzyścią dla innych: -Fustawia separator pól i NFodnosi się do liczby pól w danym wierszu. Ponieważ {statement}do warunku nie jest dołączony żaden blok kodu NF == 11, domyślnym działaniem jest wydrukowanie linii. (@cuonglm, jeśli chcesz, dołącz to wyjaśnienie).
Wildcard,
4
+1: Bardzo eleganckie i czytelne rozwiązanie, które jest również bardzo ogólne. Mogę np. Znaleźć wszystkie zniekształcone linie za pomocąawk -F , 'NF != 11' <file
Miroslav Sabo
@gardenhead: Łatwo go zdobyć, jak widać OP powiedział w swoim komentarzu. Czasami odpowiadam z telefonu komórkowego, więc trudno jest dodać szczegółowe wyjaśnienie.
cuonglm
1
@mikeserv: Nie, przepraszam, jeśli sprawiłem, że się zdezorientowałem, to tylko mój zły angielski. Nie możesz mieć 11 pól z 1-9 przecinkami.
cuonglm
1
@OlivierDulac: Chroni cię przed uruchomieniem pliku -lub nazwaniem -.
cuonglm
8

Za pomocą egrep(lub grep -Ew POSIX):

egrep "^([^,]*,){10}[^,]*$" file.csv

Odfiltrowuje to, co nie zawiera 10 przecinków: dopasowuje pełne linie ( ^na początku i $na końcu), zawierające dokładnie dziesięć powtórzeń ( {10}) sekwencji „dowolna liczba znaków oprócz”, ”, po których następuje pojedynczy„, ”„ ( ([^,]*,)), a następnie ponownie dowolna liczba znaków oprócz „,” ( [^,]*).

Możesz także użyć -xparametru, aby upuścić kotwice:

grep -xE "([^,]*,){10}[^,]*" file.csv

To jest mniej wydajna niż cuonglm „s awkrozwiązanie chociaż; ten drugi jest zazwyczaj sześciokrotnie szybszy w moim systemie dla linii z około 10 przecinkami. Dłuższe linie spowodują ogromne spowolnienia.

Stephen Kitt
źródło
5

Najprostszy grepkod, który będzie działał:

grep -xE '([^,]*,){10}[^,]*'

Wyjaśnienie:

-xzapewnia, że ​​wzór musi pasować do całej linii, a nie tylko jej części. Jest to ważne, aby nie dopasowywać wierszy zawierających więcej niż 10 przecinków.

-E oznacza „rozszerzone wyrażenie regularne”, co powoduje, że w wyrażeniu regularnym występuje mniej ucieczki odwrotnego ukośnika.

Nawiasy są używane do grupowania, a {10}następnie oznacza, że ​​musi być dokładnie dziesięć dopasowań w rzędzie wzorca w nawiasach.

[^,]jest klasą znaków - na przykład [c-f]pasuje do każdego pojedynczego znaku, który jest a c, a d, elub an f, i [^A-Z]pasuje do każdego pojedynczego znaku, który NIE jest wielką literą. [^,]Dopasowuje więc dowolny pojedynczy znak oprócz przecinka.

Klasa *po znaku oznacza „zero lub więcej z nich”.

Zatem część wyrażenia regularnego ([^,]*,)oznacza „Dowolny znak oprócz przecinka dowolną liczbę razy (w tym zero razy), po którym następuje przecinek” i {10}określa 10 z nich. Następnie [^,]*dopasuj resztę znaków niebędących przecinkami do końca wiersza.

Dzika karta
źródło
5
sed -ne's/,//11;t' -e's/,/&/10p' <in >out

To najpierw rozgałęzia dowolny wiersz z 11 lub więcej przecinkami, a następnie drukuje tylko te, które pasują do 10 przecinków.

Najwyraźniej odpowiedziałem na to wcześniej ... Oto plagiat z pytania szukającego dokładnie 4 wystąpień jakiegoś wzorca:

Możesz zaatakować [num]występowanie wzorca za pomocą s///polecenia sed ubstitution, po prostu dodając [num]do polecenia. Gdy oceniasz tpomyślne zastąpienie i nie określasz :etykiety docelowej , test wychodzi poza skrypt. Oznacza to, że wystarczy przetestować s///5przecinek lub więcej przecinków, a następnie wydrukować to, co pozostanie.

A przynajmniej obsługuje linie przekraczające maksimum 4. Najwyraźniej masz również minimalne wymagania. Na szczęście jest to tak samo proste:

sed -ne 's|,||5;t' -e 's||,|4p'

... po prostu zamień 4-te wystąpienie ,linii na siebie i przypnij pswój s///podstęp do flag ubstitution. Ponieważ wszystkie linie pasujące ,5 lub więcej razy zostały już przycięte, linie zawierające 4 ,dopasowania zawierają tylko 4.

mikeserv
źródło
1
@cuonglm - na początku tak właśnie miałem, ale ludzie zawsze mówią mi, że powinienem pisać bardziej czytelny kod. ponieważ mogę czytać rzeczy, które inni kwestionują jako nieczytelne, nie jestem pewien, co zachować, a co upuścić ...? więc wstawiłem drugi przecinek.
mikeserv
@cuonglm - możesz ze mnie kpić - to nie zrani moich uczuć. mogę wziąć żart. jeśli kpiłeś ze mnie, to było trochę zabawne. jest ok - po prostu nie byłem pewien i chciałem wiedzieć. moim zdaniem ludzie powinni móc się z siebie śmiać. w każdym razie nadal nie rozumiem!
mikeserv
Haha, racja, to bardzo pozytywne myślenie. W każdym razie bardzo zabawnie jest z tobą rozmawiać, a czasem stresujesz mój mózg.
cuonglm
To ciekawe, że w tej odpowiedzi , jeśli mogę wymienić s/hello/world/2z s//world/2GNU sed działać prawidłowo. Z dwoma sedz pamiątki, /usr/5bin/posix/sedpodnieś segfault, /usr/5bin/sedprzechodzi w bezokolicznik.
cuonglm
@ mikeserv, w nawiązaniu do naszej wcześniejszej dyskusji na temat sediawk (w komentarzach) - podoba mi się ta odpowiedź i głosowałem za nią, ale zauważam, że tłumaczenie zaakceptowanej awkodpowiedzi brzmi: „Drukuj wiersze z 11 polami”, a tłumaczenie tej sedodpowiedzi brzmi: „ Spróbuj usunąć 11. przecinek; w razie niepowodzenia przejdź do następnego wiersza. Spróbuj zamienić 10. przecinek na siebie; wydrukuj wiersz, jeśli ci się powiedzie. ” awkOdpowiedź daje instrukcje do komputera tak, jak byś je wyrazić w języku angielskim. ( awkjest dobry dla danych w terenie).
Wildcard
4

Rzucając krótkie python:

#!/usr/bin/env python2
with open('file.csv') as f:
    print '\n'.join(line for line in f if line.count(',') == 10)

Spowoduje to odczytanie każdej linii i sprawdzenie, czy liczba przecinków w linii jest równa 10 line.count(',') == 10, jeśli tak, wydrukuje to linię.

heemayl
źródło
2

A oto sposób Perla:

perl -F, -ane 'print if $#F==10'

-nPowoduje perlczytać swój wiersz po wierszu pliku wejściowego i wykonać skrypt podany przez -ena każdej linii. Do -awłącza automatyczną podziału: każda linia wejściowa zostanie podzielona na wartości podanej przez -F(tu przecinek) i zapisany jako tablicy @F.

$#F(Lub, bardziej ogólnie $#array) jest najwyższy wskaźnik tablicy @F. Ponieważ tablice zaczynają się 0linia z 11 pól będzie mieć @Fod 10. Dlatego skrypt wypisuje wiersz, jeśli ma dokładnie 11 pól.

terdon
źródło
Możesz także zrobić, print if @F==11ponieważ tablica w kontekście skalarnym zwraca liczbę elementów.
Sobrique
1

Jeśli pola mogą zawierać przecinki lub znaki nowej linii, twój kod musi zrozumieć csv. Przykład (z trzema kolumnami):

$ cat filter.csv
a,b,c
d,"e,f",g
1,2,3,4
one,two,"three
...continued"

$ cat filter.csv | python3 -c 'import sys, csv
> csv.writer(sys.stdout).writerows(
> row for row in csv.reader(sys.stdin) if len(row) == 3)
> '
a,b,c
d,"e,f",g
one,two,"three
...continued"

Przypuszczam, że większość dotychczasowych rozwiązań odrzuciłaby drugi i czwarty rząd.

Peter Otten
źródło