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.