Jak usunąć zduplikowane wiersze w pliku bez sortowania go w systemie Unix?

148

Czy istnieje sposób na usunięcie zduplikowanych wierszy w pliku w systemie Unix?

Mogę to zrobić za pomocą poleceń sort -ui uniq, ale chcę użyć sedlub awk. Czy to jest możliwe?

Vijay
źródło
12
jeśli masz na myśli kolejne duplikaty, uniqwystarczy sam.
Michael Krelin - haker
a poza tym uważam, że jest to możliwe w przypadku awkwiększych plików, ale będzie to wymagało sporo zasobów.
Michael Krelin - haker
Duplikaty stackoverflow.com/q/24324350 i stackoverflow.com/q/11532157 zawierają interesujące odpowiedzi, które najlepiej byłoby przenieść tutaj.
tripleee

Odpowiedzi:

302
awk '!seen[$0]++' file.txt

seenjest tablicą asocjacyjną, do której Awk przekaże każdą linię pliku. Jeśli linia nie znajduje się w tablicy, seen[$0]zostanie obliczona jako fałsz. !Jest operator logiczny NOT i odwrócić false na true. Awk wypisze wiersze, w których wyrażenie przyjmuje wartość true. Te ++przyrosty seentak, że seen[$0] == 1po raz pierwszy wiersz zostanie znaleziony, a następnie seen[$0] == 2, i tak dalej.
Awk ocenia wszystko oprócz 0i ""(pusty ciąg) na wartość true. Jeśli zostanie wstawiony zduplikowany wiersz, seenwówczas !seen[$0]zostanie obliczony jako fałsz i wiersz nie zostanie zapisany na wyjściu.

Jonas Elfström
źródło
5
Aby zapisać go w pliku, możemy to zrobićawk '!seen[$0]++' merge_all.txt > output.txt
Akash Kandpal
5
Ważne ostrzeżenie: jeśli musisz to zrobić dla wielu plików i dodasz więcej plików na końcu polecenia lub użyjesz symbolu wieloznacznego… tablica „widziana” zapełni się zduplikowanymi wierszami ze WSZYSTKICH plików. Jeśli zamiast tego chcesz traktować każdy plik niezależnie, musisz zrobić coś takiegofor f in *.txt; do gawk -i inplace '!seen[$0]++' "$f"; done
Nick K9,
@ NickK9, które skumulowane usuwanie dupleksu w wielu plikach jest niesamowite samo w sobie. Niezła wskazówka
sfscs
32

Z http://sed.sourceforge.net/sed1line.txt : (Nie pytaj mnie, jak to działa ;-))

 # delete duplicate, consecutive lines from a file (emulates "uniq").
 # First line in a set of duplicate lines is kept, rest are deleted.
 sed '$!N; /^\(.*\)\n\1$/!P; D'

 # delete duplicate, nonconsecutive lines from a file. Beware not to
 # overflow the buffer size of the hold space, or else use GNU sed.
 sed -n 'G; s/\n/&&/; /^\([ -~]*\n\).*\n\1/d; s/\n//; h; P'
Andre Miller
źródło
geekery ;-) +1, ale zużycie zasobów jest nieuniknione.
Michael Krelin - haker
3
'$! N; /^(.*)\n\1$/!P; D 'oznacza "Jeśli nie jesteś w ostatnim wierszu, przeczytaj inny wiersz. Teraz spójrz na to, co masz i jeśli NIE JEST to rzecz, po której następuje nowa linia, a następnie ponownie to samo, wydrukuj to. Teraz usuń rzeczy (do nowej linii). "
Beta
2
'SOL; s / \ n / && /; / ^ ([- ~] * \ n). * \ n \ 1 / d; s / \ n //; h; P 'oznacza w przybliżeniu „Dołącz całą przestrzeń do przechowywania w tym wierszu, a następnie, jeśli zobaczysz zduplikowaną linię, wyrzuć całość, w przeciwnym razie skopiuj cały bałagan z powrotem do miejsca przechowywania i wydrukuj pierwszą część (która jest właśnie linią czytaj. ”
Beta
Czy ta $!część jest konieczna? Nie sed 'N; /^\(.*\)\n\1$/!P; D'robi tego samego? Nie mogę wymyślić przykładu, w którym oba są różne na moim komputerze (fwiw próbowałem na końcu pustego wiersza w obu wersjach i obie były w porządku).
eddi
1
Prawie 7 lat później nikt nie odpowiedział na @amichair ... <sniff> mnie zasmuca. ;) W każdym razie [ -~]reprezentuje zakres znaków ASCII od 0x20 (spacja) do 0x7E (tylda). Są one uważane za drukowalne znaki ASCII (strona połączona ma również 0x7F / delete, ale to nie wydaje się właściwe). To sprawia, że ​​rozwiązanie jest zepsute dla każdego, kto nie używa ASCII lub kto używa, powiedzmy, znaków tabulacji. Bardziej przenośny [^\n]zawiera o wiele więcej znaków ... wszystkie z wyjątkiem jednego, w rzeczywistości.
Warstwa B,
15

Jednowierszowy Perl podobny do rozwiązania awk @ jonas:

perl -ne 'print if ! $x{$_}++' file

Ta odmiana usuwa końcowe spacje przed porównaniem:

perl -lne 's/\s*$//; print if ! $x{$_}++' file

Ta odmiana edytuje plik lokalnie:

perl -i -ne 'print if ! $x{$_}++' file

Ta odmiana edytuje plik w miejscu i tworzy kopię zapasową file.bak

perl -i.bak -ne 'print if ! $x{$_}++' file
Chris Koknat
źródło
7

Alternatywny sposób używania Vima (kompatybilny z Vi) :

Usuń zduplikowane, kolejne wiersze z pliku:

vim -esu NONE +'g/\v^(.*)\n\1$/d' +wq

Usuń zduplikowane, niepuste i niepuste wiersze z pliku:

vim -esu NONE +'g/\v^(.+)$\_.{-}^\1$/d' +wq

Bohr
źródło
6

Jedna linijka, którą Andre Miller opublikował powyżej, działa z wyjątkiem ostatnich wersji seda, w których plik wejściowy kończy się pustą linią i brakiem znaków. Na moim Macu mój procesor po prostu się obraca.

Nieskończona pętla, jeśli ostatnia linia jest pusta i nie ma żadnych znaków :

sed '$!N; /^\(.*\)\n\1$/!P; D'

Nie zawiesza się, ale tracisz ostatnią linię

sed '$d;N; /^\(.*\)\n\1$/!P; D'

Wyjaśnienie znajduje się na samym końcu FAQ seda :

Opiekun GNU sed uważał, że pomimo problemów z przenośnością, jakie
by to spowodowało, zmiana polecenia N, aby wydrukować (zamiast
usuwać) przestrzeń wzorców była bardziej zgodna z intuicją
dotyczącą tego, jak powinno się zachować polecenie „dołączenia następnej linii” .
Kolejnym faktem przemawiającym za zmianą było to, że "{N; polecenie;}"
usunie ostatnią linię, jeśli plik ma nieparzystą liczbę linii, ale
wypisze ostatnią, jeśli plik ma parzystą liczbę linii.

Aby przekonwertować skrypty, które używały poprzedniego zachowania N (usuwając
przestrzeń wzorca po osiągnięciu EOF) na skrypty kompatybilne ze
wszystkimi wersjami seda, zmień samotne „N”; na „$ d; N;” .

Bradley Kreider
źródło
4

Pierwsze rozwiązanie również pochodzi z http://sed.sourceforge.net/sed1line.txt

$ echo -e '1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5' |sed -nr '$!N;/^(.*)\n\1$/!P;D'
1
2
3
4
5

podstawową ideą jest:

print ONLY once of each duplicate consecutive lines at its LAST appearance and use D command to implement LOOP.

Wyjaśnia:

  1. $!N;: jeśli bieżąca linia NIE jest ostatnią linią, użyj Npolecenia, aby wczytać następną linię pattern space.
  2. /^(.*)\n\1$/!P: jeśli zawartość bieżącej pattern spacejest duplicate stringoddzielona dwoma \n, co oznacza, że ​​następna linia jest samelinią bieżącą, NIE możemy jej wydrukować zgodnie z naszą podstawową ideą; inaczej, co oznacza, że bieżący wiersz to ostatni wygląd wszystkich jego duplikatów kolejnych linii, możemy teraz użyć Ppolecenia drukowania znaków w bieżącym pattern spaceutil \n( \nrównież drukowane).
  3. D: Używamy Dpolecenia, aby usunąć znaki w bieżącym pattern spaceutil \n( \ntakże usuniętych), a następnie treść pattern spacejest następna linia.
  4. a Dpolecenie wymusi sedprzejście do swojego FIRSTpolecenia $!N, ale NIE odczyta następnego wiersza z pliku lub standardowego strumienia wejściowego.

Drugie rozwiązanie jest łatwe do zrozumienia (ode mnie):

$ echo -e '1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5' |sed -nr 'p;:loop;$!N;s/^(.*)\n\1$/\1/;tloop;D'
1
2
3
4
5

podstawową ideą jest:

print ONLY once of each duplicate consecutive lines at its FIRST appearance and use : command & t command to implement LOOP.

Wyjaśnia:

  1. odczytaj nowy wiersz ze strumienia wejściowego lub pliku i wydrukuj go raz.
  2. użyj :looppolecenia ustaw labelnazwany loop.
  3. użyj, Naby przeczytać następny wiersz w pliku pattern space.
  4. użyj s/^(.*)\n\1$/\1/do usunięcia bieżącej linii, jeśli następna linia jest taka sama jak bieżąca linia, używamy spolecenia, aby wykonać deleteakcję.
  5. jeśli spolecenie zostanie wykonane pomyślnie, użyj tlooppolecenia force, sedaby przeskoczyć do labelnazwanego loop, co spowoduje wykonanie tej samej pętli do następnych wierszy, przy czym nie ma zduplikowanych kolejnych wierszy linii, która jest latest printed; w przeciwnym razie użyj Dpolecenia do deletelinii, która jest taka sama jak latest-printed linei wymuś sedprzejście do pierwszego polecenia, które jest ppoleceniem, zawartość bieżącej pattern spacejest następną nową linią.
Weike
źródło
to samo polecenie w systemie Windows z busybox:busybox echo -e "1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5" | busybox sed -nr "$!N;/^(.*)\n\1$/!P;D"
scavenger
1

Można to osiągnąć za pomocą awk
Below Line wyświetli unikalne wartości

awk file_name | uniq

Te unikalne wartości można umieścić w nowym pliku

awk file_name | uniq > uniq_file_name

nowy plik uniq_file_name będzie zawierał tylko unikalne wartości, bez duplikatów

Aashutosh
źródło
1

uniq dałby się zwieść końcowym spacjom i tabulatorom. Aby naśladować sposób, w jaki człowiek dokonuje porównania, przed porównaniem przycinam wszystkie końcowe spacje i tabulatory.

Myślę, że $! N; potrzebuje nawiasów klamrowych, bo inaczej to trwa, i to jest przyczyną nieskończonej pętli.

Mam bash 5.0 i sed 4.7 w Ubuntu 20.10. Druga linijka nie działała przy dopasowaniu zestawu znaków.

Trzy warianty, pierwsza eliminująca sąsiednie powtarzające się wiersze, druga eliminująca powtarzające się wiersze wszędzie tam, gdzie występują, trzecia eliminująca wszystkie z wyjątkiem ostatniego wystąpienia wierszy w pliku.

pastebin

# First line in a set of duplicate lines is kept, rest are deleted.
# Emulate human eyes on trailing spaces and tabs by trimming those.
# Use after norepeat() to dedupe blank lines.

dedupe() {
 sed -E '
  $!{
   N;
   s/[ \t]+$//;
   /^(.*)\n\1$/!P;
   D;
  }
 ';
}

# Delete duplicate, nonconsecutive lines from a file. Ignore blank
# lines. Trailing spaces and tabs are trimmed to humanize comparisons
# squeeze blank lines to one

norepeat() {
 sed -n -E '
  s/[ \t]+$//;
  G;
  /^(\n){2,}/d;
  /^([^\n]+).*\n\1(\n|$)/d;
  h;
  P;
  ';
}

lastrepeat() {
 sed -n -E '
  s/[ \t]+$//;
  /^$/{
   H;
   d;
  };
  G;
  # delete previous repeated line if found
  s/^([^\n]+)(.*)(\n\1(\n.*|$))/\1\2\4/;
  # after searching for previous repeat, move tested last line to end
  s/^([^\n]+)(\n)(.*)/\3\2\1/;
  $!{
   h;
   d;
  };
  # squeeze blank lines to one
  s/(\n){3,}/\n\n/g;
  s/^\n//;
  p;
 ';
}
BobDodds
źródło
-4
cat filename | sort | uniq -c | awk -F" " '$1<2 {print $2}'

Usuwa zduplikowane wiersze za pomocą awk.

Sadhun
źródło
1
To zakłóci porządek linii.
Vijay
1
Co to jest plik tekstowy o rozmiarze 20 GB? Za wolno.
Alexander Lubyagin
Jak zawsze, jest bezużyteczny. W każdym razie robi to już samodzielnie i nie wymaga, aby dane wejściowe zawierały dokładnie jedno słowo w wierszu. catuniq
tripleee