Czy istnieje sposób na „uniq” według kolumny?


195

Mam plik .csv taki jak ten:

stack2@example.com,2009-11-27 01:05:47.893000000,example.net,127.0.0.1
overflow@example.com,2009-11-27 00:58:29.793000000,example.net,255.255.255.0
overflow@example.com,2009-11-27 00:58:29.646465785,example.net,256.255.255.0
...

Muszę usunąć zduplikowane wiadomości e-mail (całą linię) z pliku (tj. Jedną z linii zawartych overflow@example.comw powyższym przykładzie). Jak używać uniqtylko na polu 1 (oddzielone przecinkami)? Według man, uniqnie ma opcji dla kolumn.

Próbowałem czegoś, sort | uniqale to nie działa.

Odpowiedzi:


326
sort -u -t, -k1,1 file
  • -u za wyjątkowe
  • -t, więc przecinek jest separatorem
  • -k1,1 dla pola klucza 1

Wynik testu:

overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0 
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1 

3
to nie działa, jeśli kolumna zawiera sam przecinek (z cytatem)
— user775187

13
dlaczego potrzebujesz 1 w -k1,1? dlaczego nie tylko -k1?
— hello_there_andy

18
@hello_there_andy: Zostało to wyjaśnione w instrukcji ( man sort). Oznacza pozycję początkową i końcową.
— Serrano

3
@CarlSmotricz: Przetestowałem go i potwierdził to, co sortmówi „s podręcznika:« ze należy sprawdzić, a bez ścisłej kolejności , wyjście tylko pierwszy z taką samą metę .» Jest to zatem „pierwsze wystąpienie duplikatu przed sortowaniem”. -u--unique-c-c
— Geremia

2
to również zmienia kolejność linii, prawda?
— rkachach

103
awk -F"," '!_[$1]++' file
  • -F ustawia separator pól.
  • $1 jest pierwszym polem.
  • _[val]wyszukuje valskrót _(zmienna regularna).
  • ++ zwiększ i zwróć starą wartość.
  • ! zwraca logiczne nie.
  • na końcu jest niejawny wydruk.

4
To podejście jest dwa razy szybsze niż sortowanie
— bitek

9
Ma to również dodatkową zaletę polegającą na utrzymaniu linii w oryginalnym porządku!
— AffluentOwl

8
Jeśli potrzebujesz ostatniego uniq zamiast pierwszego, ten skrypt awk pomoże:awk -F',' '{ x[$1]=$0 } END { for (i in x) print x[i] }' file
— Sukima

3
@eshwar wystarczy dodać więcej pól do indeksu słownika! Na przykład !_[$1][$2]++można użyć do sortowania według pierwszych dwóch pól. Mój awk-fu nie jest jednak wystarczająco silny, aby móc wyróżniać się na wielu polach. :(
— Soham Chowdhury

1
Znakomity! ta opcja jest lepsza niż odpowiedź, ponieważ utrzymuje porządek linii
— rkachach

16

Aby rozważyć wiele kolumn.

Sortuj i podaj unikalną listę na podstawie kolumny 1 i kolumny 3:

sort -u -t : -k 1,1 -k 3,3 test.txt
  • -t : dwukropek jest separatorem
  • -k 1,1 -k 3,3 na podstawie kolumny 1 i kolumny 3

8

lub jeśli chcesz użyć uniq:

<mycvs.cvs tr -s ',' ' ' | awk '{print $3" "$2" "$1}' | uniq -c -f2

daje:

1 01:05:47.893000000 2009-11-27 tack2@domain.com
2 00:58:29.793000000 2009-11-27 overflow@domain2.com
1

5
Chciałbym zwrócić uwagę na możliwe uproszczenie: Możesz rzucić cat! Zamiast pipować do tr, po prostu pozwól tr odczytać plik za pomocą <. Rurociągi catto powszechna niepotrzebna komplikacja stosowana przez nowicjuszy. W przypadku dużych ilości danych można uzyskać efekt wydajności.
— Carl Smotricz

4
Dobrze wiedzieć. Dzięki! (Oczywiście ma to sens, myśląc o „kocie” i „lenistwie”;))
— Carsten C.

Odwracanie pól można uprościć za pomocą rev.
— Hielke Walinga

5

Jeśli chcesz zachować ostatni z duplikatów, którego możesz użyć

 tac a.csv | sort -u -t, -r -k1,1 |tac

To było moje wymaganie

tutaj

tac odwróci plik linia po linii


1

Oto bardzo fajny sposób.

Najpierw sformatuj zawartość, tak aby kolumna, którą chcesz porównać pod kątem niepowtarzalności, ma stałą szerokość. Jednym ze sposobów jest użycie awk printf ze specyfikatorem szerokości pola / kolumny („% 15s”).

Teraz można użyć opcji -f i -w uniq, aby pominąć poprzednie pola / kolumny i określić szerokość porównania (szerokość kolumny).

Oto trzy przykłady.

W pierwszym przykładzie ...

1) Tymczasowo ustaw kolumnę będącą przedmiotem zainteresowania na większą lub równą maksymalnej szerokości pola.

2) Użyj opcji -f uniq, aby pominąć poprzednie kolumny, i użyj opcji -w uniq, aby ograniczyć szerokość do tmp_fixed_width.

3) Usuń końcowe spacje z kolumny, aby „przywrócić” jej szerokość (zakładając, że wcześniej nie było końcowych spacji).

printf "%s" "$str" \
| awk '{ tmp_fixed_width=15; uniq_col=8; w=tmp_fixed_width-length($uniq_col); for (i=0;i<w;i++) { $uniq_col=$uniq_col" "}; printf "%s\n", $0 }' \
| uniq -f 7 -w 15 \
| awk '{ uniq_col=8; gsub(/ */, "", $uniq_col); printf "%s\n", $0 }'

W drugim przykładzie ...

Utwórz nową kolumnę uniq 1. Następnie usuń ją po zastosowaniu filtra uniq.

printf "%s" "$str" \
| awk '{ uniq_col_1=4; printf "%15s %s\n", uniq_col_1, $0 }' \
| uniq -f 0 -w 15 \
| awk '{ $1=""; gsub(/^ */, "", $0); printf "%s\n", $0 }'

Trzeci przykład jest taki sam jak drugi, ale dla wielu kolumn.

printf "%s" "$str" \
| awk '{ uniq_col_1=4; uniq_col_2=8; printf "%5s %15s %s\n", uniq_col_1, uniq_col_2, $0 }' \
| uniq -f 0 -w 5 \
| uniq -f 1 -w 15 \
| awk '{ $1=$2=""; gsub(/^ */, "", $0); printf "%s\n", $0 }'

-3

cóż, prostsze niż izolowanie kolumny za pomocą awk, jeśli chcesz usunąć wszystko o określonej wartości dla danego pliku, dlaczego po prostu nie zrobić grep -v:

np. aby usunąć wszystko o wartości „col2” w wierszu drugiego miejsca: col1, col2, col3, col4

grep -v ',col2,' file > file_minus_offending_lines

Jeśli nie jest to wystarczająco dobre, ponieważ niektóre wiersze mogą zostać nieprawidłowo usunięte, prawdopodobnie wyświetlając pasującą wartość w innej kolumnie, możesz zrobić coś takiego:

awk, aby wyizolować naruszającą kolumnę: np

awk -F, '{print $2 "|" $line}'

-F ustawia pole rozdzielane na „,”, 2 $ oznacza kolumnę 2, a następnie niestandardowy separator, a następnie całą linię. Następnie możesz filtrować, usuwając linie zaczynające się od wartości obrażającej:

 awk -F, '{print $2 "|" $line}' | grep -v ^BAD_VALUE

a następnie usuń elementy przed separatorem:

awk -F, '{print $2 "|" $line}' | grep -v ^BAD_VALUE | sed 's/.*|//g'

(uwaga - polecenie sed jest niedbałe, ponieważ nie zawiera wartości zmiany znaczenia. Wzorzec sed powinien być tak naprawdę taki jak „[^ |] +” (tzn. cokolwiek, co nie jest ogranicznikiem). Ale mam nadzieję, że jest to wystarczająco jasne.


3
Nie chce oczyszczać linii, chce zachować jedną kopię linii z określonym łańcuchem. Uniq to właściwy przypadek użycia.
— ingyhere

-3

Sortując sortnajpierw plik , możesz następnie zastosować uniq.

Wygląda na to, że plik jest w porządku:

$ cat test.csv
overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1

$ sort test.csv
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 
overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1

$ sort test.csv | uniq
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 
overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1

Możesz także wykonać magię AWK:

$ awk -F, '{ lines[$1] = $0 } END { for (l in lines) print lines[l] }' test.csv
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 

Nie jest to unikalne według kolumny, zgodnie z pytaniem w pytaniu. Jest to unikalne dla całej linii. Ponadto nie musisz robić nic, aby zrobić uniq. Obie wykluczają się wzajemnie.
— Javid Jamae

1
Tak masz rację. Ostatni przykład robi to, o co pytano, mimo że zaakceptowana odpowiedź jest o wiele czystsza. Jeśli chodzi o sortto uniq, sortnależy to zrobić przed zrobieniem, uniqinaczej nie zadziała (ale możesz pominąć drugie polecenie i po prostu użyć sort -u). From uniq(1): „Filtruj sąsiadujące pasujące linie z INPUT (lub standardowego wejścia), pisząc do OUTPUT (lub standardowego wyjścia).”
— Mikael S,

Ach, masz rację co do sortowania przed uniq. Nigdy nie zdawałem sobie sprawy, że uniq działa tylko na sąsiednich liniach. Chyba zawsze używam sort -u.
— Javid Jamae,
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.