Czy istnieje sposób na usunięcie zduplikowanych wierszy w pliku w systemie Unix?
Mogę to zrobić za pomocą poleceń sort -u
i uniq
, ale chcę użyć sed
lub awk
. Czy to jest możliwe?
Czy istnieje sposób na usunięcie zduplikowanych wierszy w pliku w systemie Unix?
Mogę to zrobić za pomocą poleceń sort -u
i uniq
, ale chcę użyć sed
lub awk
. Czy to jest możliwe?
awk
większych plików, ale będzie to wymagało sporo zasobów.
Odpowiedzi:
awk '!seen[$0]++' file.txt
seen
jest 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 seen
tak, że seen[$0] == 1
po raz pierwszy wiersz zostanie znaleziony, a następnie seen[$0] == 2
, i tak dalej.
Awk ocenia wszystko oprócz 0
i ""
(pusty ciąg) na wartość true. Jeśli zostanie wstawiony zduplikowany wiersz, seen
wówczas !seen[$0]
zostanie obliczony jako fałsz i wiersz nie zostanie zapisany na wyjściu.
awk '!seen[$0]++' merge_all.txt > output.txt
for f in *.txt; do gawk -i inplace '!seen[$0]++' "$f"; done
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'
$!
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).
[ -~]
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.
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
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;” .
$ 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:
$!N;
: jeśli bieżąca linia NIE jest ostatnią linią, użyj N
polecenia, aby wczytać następną linię pattern space
./^(.*)\n\1$/!P
: jeśli zawartość bieżącej pattern space
jest duplicate string
oddzielona dwoma \n
, co oznacza, że następna linia jest same
linią 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ć P
polecenia drukowania znaków w bieżącym pattern space
util \n
( \n
również drukowane).D
: Używamy D
polecenia, aby usunąć znaki w bieżącym pattern space
util \n
( \n
także usuniętych), a następnie treść pattern space
jest następna linia.D
polecenie wymusi sed
przejście do swojego FIRST
polecenia $!N
, ale NIE odczyta następnego wiersza z pliku lub standardowego strumienia wejściowego.$ 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:
:loop
polecenia ustaw label
nazwany loop
.N
aby przeczytać następny wiersz w pliku pattern space
.s/^(.*)\n\1$/\1/
do usunięcia bieżącej linii, jeśli następna linia jest taka sama jak bieżąca linia, używamy s
polecenia, aby wykonać delete
akcję.s
polecenie zostanie wykonane pomyślnie, użyj tloop
polecenia force, sed
aby przeskoczyć do label
nazwanego 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 D
polecenia do delete
linii, która jest taka sama jak latest-printed line
i wymuś sed
przejście do pierwszego polecenia, które jest p
poleceniem, zawartość bieżącej pattern space
jest następną nową linią.busybox echo -e "1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5" | busybox sed -nr "$!N;/^(.*)\n\1$/!P;D"
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.
# 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;
';
}
cat filename | sort | uniq -c | awk -F" " '$1<2 {print $2}'
Usuwa zduplikowane wiersze za pomocą awk.
cat
uniq
uniq
wystarczy sam.