Wybierz unikalne lub odrębne wartości z listy w skrypcie powłoki UNIX

238

Mam skrypt ksh, który zwraca długą listę wartości oddzielonych znakiem nowej linii i chcę widzieć tylko wartości unikalne / odrębne. Czy można to zrobić?

Załóżmy na przykład, że moje dane wyjściowe to sufiksy plików w katalogu:

tar
gz
java
gz
java
tar
class
class

Chcę zobaczyć listę taką jak:

tar
gz
java
class
Brabster
źródło

Odpowiedzi:

432

Możesz przyjrzeć się aplikacjom uniqi sort.

./yourscript.ksh | sortuj | uniq

(FYI, tak, sortowanie jest konieczne w tym wierszu poleceń, uniqusuwane są tylko zduplikowane linie, które są bezpośrednio po sobie)

EDYTOWAĆ:

W przeciwieństwie do tego, co napisał Aaron Digulla w związku z uniqopcjami wiersza poleceń:

Biorąc pod uwagę następujące dane wejściowe:

klasa
słoik
słoik
słoik
kosz
kosz
Jawa

uniq wypisze wszystkie wiersze dokładnie raz:

klasa
słoik
kosz
Jawa

uniq -d wyświetli wszystkie wiersze, które pojawiają się więcej niż raz, i wydrukuje je raz:

słoik
kosz

uniq -u wyświetli wszystkie wiersze, które pojawiają się dokładnie raz, i wydrukuje je raz:

klasa
Jawa
Matthew Scharley
źródło
2
Tylko informacja dla spóźnialskich: odpowiedź AaronaDigulli została poprawiona.
mklement0
2
bardzo dobra uwaga, że ​​ten rodzaj sortowania jest konieczny w tym wierszu poleceń, uniq usuwa tylko zduplikowane linie, które są bezpośrednio po sobie`, których właśnie się nauczyłem !!
HattrickNZ
4
GNU sortoferuje również -uwersję umożliwiającą podawanie unikalnych wartości.
Arthur2e5,
Doszedłem do wniosku, że uniqszwy przetwarzają tylko sąsiednie linie (przynajmniej domyślnie), co oznacza, że ​​można sortwprowadzić przed karmieniem uniq.
Stphane
85
./script.sh | sort -u

Jest to to samo co odpowiedź monotlenku , ale nieco bardziej zwięzłe.

gpojd
źródło
6
Jesteś skromny: Twoje rozwiązanie będzie również działać lepiej (prawdopodobnie zauważalne tylko przy dużych zestawach danych).
mklement0
Myślę, że powinno to być bardziej wydajne niż ... | sort | uniqdlatego, że wykonuje się je jednym strzałem
Adrian Antunez
10

W przypadku większych zestawów danych, w których sortowanie może nie być pożądane, możesz również użyć następującego skryptu perl:

./yourscript.ksh | perl -ne 'if (!defined $x{$_}) { print $_; $x{$_} = 1; }'

To po prostu zapamiętuje każde wyjście liniowe, aby nie wyprowadzało go ponownie.

Ma tę przewagę nad rozwiązaniem „ sort | uniq”, że nie wymaga sortowania z góry.

paxdiablo
źródło
2
Zauważ, że sortowanie bardzo dużego pliku nie jest samo w sobie problemem z sortowaniem; potrafi sortować pliki, które są większe niż dostępna pamięć RAM + swap. Perl, OTOH, zawiedzie, jeśli będzie tylko kilka duplikatów.
Aaron Digulla
1
Tak, jest to kompromis w zależności od oczekiwanych danych. Perl jest lepszy dla ogromnego zestawu danych z wieloma duplikatami (nie jest wymagana pamięć dyskowa). Ogromny zestaw danych z kilkoma duplikatami powinien używać sortowania (i pamięci dyskowej). Małe zestawy danych mogą korzystać z obu. Osobiście najpierw wypróbuję Perla, jeśli nie powiedzie się, przełącz się na sortowanie.
paxdiablo
Ponieważ sortowanie daje korzyść tylko wtedy, gdy trzeba zamienić na dysk.
paxdiablo
5
To świetnie, gdy chcę pierwsze wystąpienie każdej linii. Sortowanie to zepsuje.
Bluu,
10

Z zsh można to zrobić:

% cat infile 
tar
more than one word
gz
java
gz
java
tar
class
class
zsh-5.0.0[t]% print -l "${(fu)$(<infile)}"
tar
more than one word
gz
java
class

Lub możesz użyć AWK:

% awk '!_[$0]++' infile    
tar
more than one word
gz
java
class
Dimitre Radoulov
źródło
2
Sprytne rozwiązania, które nie wymagają sortowania danych wejściowych. Ostrzeżenia: Bardzo sprytne, ale tajemnicze awkrozwiązanie ( wyjaśnienie znajduje się na stackoverflow.com/a/21200722/45375 ) będzie działać z dużymi plikami, o ile liczba unikalnych wierszy jest wystarczająco mała (ponieważ unikatowe wiersze są przechowywane w pamięci ). zshRozwiązanie czyta cały plik do pamięci pierwszy, który nie może być opcja z dużymi plikami. Ponadto, jak napisano, poprawnie obsługiwane są tylko wiersze bez osadzonych spacji; aby to naprawić, użyj IFS=$'\n' read -d '' -r -A u <file; print -l ${(u)u}zamiast tego.
mklement0
Poprawny. Lub:(IFS=$'\n' u=($(<infile)); print -l "${(u)u[@]}")
Dimitre Radoulov
1
Dzięki, to jest prostsze (zakładając, że nie musisz ustawiać zmiennych potrzebnych poza podpowłoką). Jestem ciekawy, kiedy potrzebujesz [@]sufiksu, aby odwoływać się do wszystkich elementów tablicy - wydaje się, że - przynajmniej od wersji 5 - działa bez niej; czy dodałeś to dla jasności?
mklement0
1
@ mklement0, masz rację! Nie pomyślałem o tym, kiedy napisałem post. W rzeczywistości powinno to wystarczyć:print -l "${(fu)$(<infile)}"
Dimitre Radoulov
1
Fantastycznie, dziękuję za aktualizację twojego postu - mogłem też naprawić awkpróbkę wyjściową.
mklement0
9

Przeprowadź je przez sorti uniq. To usuwa wszystkie duplikaty.

uniq -ddaje tylko duplikaty, uniq -udaje tylko te unikalne (paski duplikaty).

Aaron Digulla
źródło
Najpierw posortuj po wyglądzie
brabster
1
Tak Lub dokładniej, musisz zgrupować wszystkie zduplikowane linie razem. Sortowanie robi to jednak z definicji;)
Matthew Scharley
Poza tym uniq -uNIE jest zachowaniem domyślnym (szczegóły w edycji w mojej odpowiedzi)
Matthew Scharley
7

Dzięki AWK możesz to zrobić, uważam, że jest to szybsze niż sortowanie

 ./yourscript.ksh | awk '!a[$0]++'
Ajak6
źródło
To zdecydowanie mój ulubiony sposób na wykonanie pracy, wielkie dzięki! Zwłaszcza w przypadku większych plików sortowanie | uniq-solutions prawdopodobnie nie jest tym, czego chcesz.
Schmitzi
1

Unikalny, zgodnie z prośbą (ale nie posortowany);
zużywa mniej zasobów systemowych na mniej niż ~ 70 elementów (zgodnie z czasem);
napisane, aby pobrać dane wejściowe ze standardowego wejścia
(lub zmodyfikować i dołączyć do innego skryptu):
(Bash)

bag2set () {
    # Reduce a_bag to a_set.
    local -i i j n=${#a_bag[@]}
    for ((i=0; i < n; i++)); do
        if [[ -n ${a_bag[i]} ]]; then
            a_set[i]=${a_bag[i]}
            a_bag[i]=$'\0'
            for ((j=i+1; j < n; j++)); do
                [[ ${a_set[i]} == ${a_bag[j]} ]] && a_bag[j]=$'\0'
            done
        fi
    done
}
declare -a a_bag=() a_set=()
stdin="$(</dev/stdin)"
declare -i i=0
for e in $stdin; do
    a_bag[i]=$e
    i=$i+1
done
bag2set
echo "${a_set[@]}"
FGrose
źródło
0

Dostaję lepsze wskazówki, aby uzyskać nieplikowe wpisy w pliku

awk '$0 != x ":FOO" && NR>1 {print x} {x=$0} END {print}' file_name | uniq -f1 -u
Mary Marty
źródło