Jak mogę uzyskać unikalne wartości z tablicy w Bash?


93

Mam prawie to samo pytanie, co tutaj .

Mam tablicę zawierającą aa ab aa ac aa aditd. Teraz chcę zaznaczyć wszystkie unikalne elementy z tej tablicy. Pomyślałem, że byłoby to proste z sort | uniqlub z, sort -ujak wspomnieli w innym pytaniu, ale nic się nie zmieniło w tablicy ... Kod to:

echo `echo "${ids[@]}" | sort | uniq`

Co ja robię źle?

Odpowiedzi:


131

Trochę hacky, ale to powinno wystarczyć:

echo "${ids[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '

Aby zapisać posortowane unikalne wyniki z powrotem w tablicy, wykonaj przypisanie tablicy :

sorted_unique_ids=($(echo "${ids[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '))

Jeśli twoja powłoka obsługuje ciągi tutaj ( bashpowinno), możesz oszczędzić echoproces, zmieniając go na:

tr ' ' '\n' <<< "${ids[@]}" | sort -u | tr '\n' ' '

Wejście:

ids=(aa ab aa ac aa ad)

Wynik:

aa ab ac ad

Wyjaśnienie:

  • "${ids[@]}"- Składnia do pracy z tablicami powłoki, używana jako część echolub ciąg znaków. W @części „oznacza wszystkie pozycje w tablicy”
  • tr ' ' '\n'- Konwertuj wszystkie spacje na znaki nowej linii. Ponieważ twoja tablica jest widziana przez powłokę jako elementy w jednym wierszu, oddzielone spacjami; a ponieważ sort oczekuje, że wejście będzie w oddzielnych wierszach.
  • sort -u - sortuj i zachowuj tylko unikalne elementy
  • tr '\n' ' ' - przekonwertuj nowe linie, które dodaliśmy wcześniej z powrotem do spacji.
  • $(...)- Zastępowanie poleceń
  • Poza tym: tr ' ' '\n' <<< "${ids[@]}"to bardziej efektywny sposób:echo "${ids[@]}" | tr ' ' '\n'

37
+1. Trochę uporządkowany: przechowuj elementy uniq w nowej tablicy:uniq=($(printf "%s\n" "${ids[@]}" | sort -u)); echo "${uniq[@]}"
glenn jackman

@glennjackman oh neat! Nie zdaje sobie sprawy, można użyć printfw ten sposób (podać więcej argumentów niż ciągi format)
Sampson-chen

4
+1 Nie jestem pewien, czy jest to odosobniony przypadek, ale oddanie unikalne przedmioty z powrotem do tablicy potrzebne dodatkowe nawiasy takie jak: sorted_unique_ids=($(echo "${ids[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ')). Bez dodatkowych nawiasów podawał jako ciąg.
co

3
Jeśli nie chcesz zmieniać kolejności elementów, użyj ... | uniq | ...zamiast ... | sort -u | ....
Jesse Chisholm

2
@Jesse, uniqusuwa tylko kolejne duplikaty. W przykładzie w tej odpowiedzi sorted_unique_idsskończy się identycznie jak oryginał ids. Aby zachować porządek, spróbuj ... | awk '!seen[$0]++'. Zobacz także stackoverflow.com/questions/1444406/… .
Rob Kennedy

29

Jeśli używasz Bash w wersji 4 lub nowszej (co powinno mieć miejsce w każdej nowoczesnej wersji Linuksa), możesz uzyskać unikalne wartości tablic w bash, tworząc nową tablicę asocjacyjną zawierającą każdą z wartości oryginalnej tablicy. Coś takiego:

$ a=(aa ac aa ad "ac ad")
$ declare -A b
$ for i in "${a[@]}"; do b["$i"]=1; done
$ printf '%s\n' "${!b[@]}"
ac ad
ac
aa
ad

To działa, ponieważ w dowolnej tablicy (asocjacyjnej lub tradycyjnej, w dowolnym języku) każdy klucz może wystąpić tylko raz. Kiedy forpętla osiąga drugą wartość aain a[2], nadpisuje b[aa]pierwotnie ustawioną wartość a[0].

Robienie rzeczy w natywnym bashu może być szybsze niż używanie potoków i zewnętrznych narzędzi, takich jak sorti uniq, chociaż w przypadku większych zestawów danych prawdopodobnie zobaczysz lepszą wydajność, jeśli użyjesz mocniejszego języka, takiego jak awk, python itp.

Jeśli czujesz się pewnie, możesz uniknąć forpętli, korzystając printfz możliwości ponownego wykorzystania formatu dla wielu argumentów, chociaż wydaje się to wymagać eval. (Przestań czytać teraz, jeśli nie masz nic przeciwko.)

$ eval b=( $(printf ' ["%s"]=1' "${a[@]}") )
$ declare -p b
declare -A b=(["ac ad"]="1" [ac]="1" [aa]="1" [ad]="1" )

Powodem, dla którego to rozwiązanie wymaga, evaljest to, że wartości tablic są określane przed podziałem na słowa. Oznacza to, że wynik podstawienia polecenia jest traktowany jako pojedyncze słowo a nie zestaw par klucz = wartość.

Chociaż używa podpowłoki, używa tylko wbudowanych bash do przetwarzania wartości tablic. Pamiętaj, aby evalkrytycznym okiem ocenić swoje użycie . Jeśli nie masz 100% pewności, że chepner, glenn jackman lub greycat nie znajdzie żadnych błędów w twoim kodzie, użyj zamiast tego pętli for.


powoduje błąd: przekroczono poziom rekursji wyrażenia
Benubird,

1
@Benubird - czy możesz wkleić zawartość swojego terminala? U mnie działa idealnie, więc przypuszczam, że masz (1) literówkę, (2) starszą wersję basha (tablice asocjacyjne zostały dodane do wersji 4) lub (3) absurdalnie duży napływ kosmicznego tła promieniowanie spowodowane przez kwantową czarną dziurę w piwnicy twojego sąsiada, generujące zakłócenia w sygnałach w twoim komputerze.
ghoti

1
nie mogę, nie zatrzymałem tego, który nie działał. ale, próbowałem teraz uruchomić twój i zadziałało, więc prawdopodobnie sprawa z promieniowaniem kosmicznym.
Benubird

zgadywanie, że ta odpowiedź wykorzystuje bash v4 (tablice asocjacyjne) i jeśli ktoś spróbuje w bash v3, to nie zadziała (prawdopodobnie nie to, co @Benubird widział). Bash v3 jest nadal domyślny w wielu środowiskach
nhed

1
@nhed, punkt zajęty. Widzę, że mój aktualny Macbook Yosemite ma tę samą wersję w bazie, chociaż zainstalowałem v4 z macports. To pytanie jest oznaczone jako „linux”, ale zaktualizowałem moją odpowiedź, aby wskazać na wymaganie.
ghoti

18

Zdaję sobie sprawę, że odpowiedź na to pytanie została już udzielona, ​​ale pojawiło się dość wysoko w wynikach wyszukiwania i może komuś pomóc.

printf "%s\n" "${IDS[@]}" | sort -u

Przykład:

~> IDS=( "aa" "ab" "aa" "ac" "aa" "ad" )
~> echo  "${IDS[@]}"
aa ab aa ac aa ad
~>
~> printf "%s\n" "${IDS[@]}" | sort -u
aa
ab
ac
ad
~> UNIQ_IDS=($(printf "%s\n" "${IDS[@]}" | sort -u))
~> echo "${UNIQ_IDS[@]}"
aa ab ac ad
~>

1
aby naprawić tablicę byłem zmuszony to zrobić :, ids=(ab "a a" ac aa ad ac aa);IFS=$'\n' ids2=(`printf "%s\n" "${ids[@]}" |sort -u`)więc dodałem IFS=$'\n'sugerowane przez @gniourf_gniourf
Aquarius Power

Musiałem też wykonać kopię zapasową i po wydaniu komendy przywrócić wartość IFS! albo psuje inne rzeczy ...
Aquarius Power

@Jetse To powinna być akceptowana odpowiedź, ponieważ używa tylko dwóch poleceń, bez pętli, bez eval i jest najbardziej kompaktową wersją.
mgutt

1
@AquariusPower Ostrożnie, w zasadzie robisz:, IFS=$'\n'; ids2=(...)ponieważ tymczasowe przypisanie przed przypisaniem zmiennych nie jest możliwe. Zamiast korzystać z tej konstrukcji: IFS=$'\n' read -r -a ids2 <<<"$(printf "%s\n" "${ids[@]}" | sort -u)".
Yeti

13

Jeśli elementy tablicy mają białe spacje lub jakikolwiek inny znak specjalny powłoki (i czy możesz być pewien, że tak nie jest?), To aby je najpierw uchwycić (i zawsze powinieneś to robić), wyrażaj tablicę w podwójnych cudzysłowach! np "${a[@]}". Bash dosłownie zinterpretuje to jako „każdy element tablicy w osobnym argumencie” ”. W bashu to po prostu zawsze działa, zawsze.

Następnie, aby uzyskać posortowaną (i unikalną) tablicę, musimy przekonwertować ją na format, który rozumie sortowanie i być w stanie przekonwertować ją z powrotem na elementy tablicy bash. Oto najlepsze, co wymyśliłem:

eval a=($(printf "%q\n" "${a[@]}" | sort -u))

Niestety, kończy się to niepowodzeniem w specjalnym przypadku pustej tablicy, przekształcając pustą tablicę w tablicę zawierającą 1 pusty element (ponieważ printf miał 0 argumentów, ale nadal drukuje tak, jakby miał jeden pusty argument - patrz wyjaśnienie). Więc musisz to złapać w „jeśli” lub czymś.

Objaśnienie: Format% q dla printf "powłoki ucieka" z wypisanego argumentu, w taki sposób, że bash może odzyskać w czymś takim jak eval! Ponieważ każdy element jest wypisywany w powłoce ze znakami ucieczki w swoim własnym wierszu, jedynym separatorem między elementami jest znak nowej linii, a przypisanie tablicy przyjmuje każdy wiersz jako element, przetwarzając wartości ucieczki na tekst literału.

na przykład

> a=("foo bar" baz)
> printf "%q\n" "${a[@]}"
'foo bar'
baz
> printf "%q\n"
''

Wartość eval jest konieczna, aby usunąć ucieczkę z każdej wartości wracającej do tablicy.


To jedyny kod, który działał dla mnie, ponieważ moja tablica ciągów miała spacje. % Q jest tym, co załatwiło sprawę. Dzięki :)
Somaiah Kumbera

A jeśli nie chcesz zmieniać kolejności elementów, użyj uniqzamiast sort -u.
Jesse Chisholm

Należy pamiętać, że uniqnie działa poprawnie w przypadku niesortowanych list, dlatego należy go zawsze używać w połączeniu z sort.
Jean Paul,

uniq na nieposortowanej liście usunie kolejne duplikaty. Nie usunie identycznych elementów listy oddzielonych czymś innym. uniq może być przydatny w zależności od oczekiwanych danych i chęci zachowania pierwotnego porządku.
vontrapp

10

'sort' może być użyte do uporządkowania wyników pętli for:

for i in ${ids[@]}; do echo $i; done | sort

i usuń duplikaty za pomocą „-u”:

for i in ${ids[@]}; do echo $i; done | sort -u

Wreszcie możesz po prostu nadpisać swoją tablicę unikalnymi elementami:

ids=( `for i in ${ids[@]}; do echo $i; done | sort -u` )

A jeśli nie chcesz zmieniać kolejności tego, co zostało, nie musisz:ids=( `for i in ${ids[@]}; do echo $i; done | uniq` )
Jesse Chisholm

3

ten też zachowa porządek:

echo ${ARRAY[@]} | tr [:space:] '\n' | awk '!a[$0]++'

i zmodyfikować oryginalną tablicę z unikalnymi wartościami:

ARRAY=($(echo ${ARRAY[@]} | tr [:space:] '\n' | awk '!a[$0]++'))

Nie używaj uniq. Wymaga sortowania, gdy awk tego nie robi, a celem tej odpowiedzi jest zachowanie kolejności, gdy dane wejściowe nie są posortowane.
bukzor

2

Aby utworzyć nową tablicę składającą się z unikalnych wartości, upewnij się, że tablica nie jest pusta, a następnie wykonaj jedną z następujących czynności:

Usuń zduplikowane wpisy (z sortowaniem)

readarray -t NewArray < <(printf '%s\n' "${OriginalArray[@]}" | sort -u)

Usuń zduplikowane wpisy (bez sortowania)

readarray -t NewArray < <(printf '%s\n' "${OriginalArray[@]}" | awk '!x[$0]++')

Ostrzeżenie: nie próbuj robić czegoś takiego NewArray=( $(printf '%s\n' "${OriginalArray[@]}" | sort -u) ). Będzie pękać na przestrzeniach.


Usuwanie zduplikowanych wpisów (bez sortowania) jest takie samo jak (z sortowaniem), z wyjątkiem zmiany, sort -uktóra ma być uniq.
Jesse Chisholm

@JesseChisholm uniqscala tylko zduplikowane linie, które sąsiadują, więc nie jest tym samym, co awk '!x[$0]++'.
Szósty

@JesseChisholm Proszę usunąć mylący komentarz.
bukzor

2

numer kota.txt

1 2 3 4 4 3 2 5 6

wypisz wiersz w kolumnie: cat number.txt | awk '{for(i=1;i<=NF;i++) print $i}'

1
2
3
4
4
3
2
5
6

znajdź zduplikowane rekordy: cat number.txt | awk '{for(i=1;i<=NF;i++) print $i}' |awk 'x[$0]++'

4
3
2

Zastąp zduplikowane rekordy: cat number.txt | awk '{for(i=1;i<=NF;i++) print $i}' |awk '!x[$0]++'

1
2
3
4
5
6

Znajdź tylko rekordy Uniq: cat number.txt | awk '{for(i=1;i<=NF;i++) print $i|"sort|uniq -u"}

1
5
6

1

Bez utraty oryginalnego zamówienia:

uniques=($(tr ' ' '\n' <<<"${original[@]}" | awk '!u[$0]++' | tr '\n' ' '))

1

Jeśli potrzebujesz rozwiązania wykorzystującego tylko wewnętrzne funkcje bash, możesz ustawić wartości jako klucze w tablicy asocjacyjnej, a następnie wyodrębnić klucze:

declare -A uniqs
list=(foo bar bar "bar none")
for f in "${list[@]}"; do 
  uniqs["${f}"]=""
done

for thing in "${!uniqs[@]}"; do
  echo "${thing}"
done

To wyjdzie

bar
foo
bar none

Właśnie zauważyłem, że jest to zasadniczo to samo, co odpowiedź @ghotis powyżej, z wyjątkiem tego, że jego rozwiązanie nie uwzględnia elementów listy ze spacjami.
rln

Słuszna uwaga. Dodałem cudzysłowy do mojego rozwiązania, więc teraz obsługuje spacje. Pierwotnie napisałem go tylko po to, aby poradzić sobie z przykładowymi danymi w pytaniu, ale zawsze dobrze jest uwzględnić takie nieprzewidziane okoliczności. Dzieki za sugestie.
ghoti

1

Inną opcją radzenia sobie z osadzonymi białymi znakami jest oddzielenie go od wartości null printf, odróżnienie za pomocą sort, a następnie użycie pętli do spakowania go z powrotem do tablicy:

input=(a b c "$(printf "d\ne")" b c "$(printf "d\ne")")
output=()

while read -rd $'' element
do 
  output+=("$element")
done < <(printf "%s\0" "${input[@]}" | sort -uz)

Na końcu inputi outputzawierają żądane wartości (pod warunkiem, że kolejność nie jest ważna):

$ printf "%q\n" "${input[@]}"
a
b
c
$'d\ne'
b
c
$'d\ne'

$ printf "%q\n" "${output[@]}"
a
b
c
$'d\ne'

1

A co z tą odmianą?

printf '%s\n' "${ids[@]}" | sort -u

A potem sorted_arr=($(printf '%s\n' "${ids[@]}" | sort -u).
glony

0

Spróbuj tego, aby uzyskać unikalne wartości dla pierwszej kolumny w pliku

awk -F, '{a[$1];}END{for (i in a)print i;}'

-3
# Read a file into variable
lines=$(cat /path/to/my/file)

# Go through each line the file put in the variable, and assign it a variable called $line
for line in $lines; do
  # Print the line
  echo $line
# End the loop, then sort it (add -u to have unique lines)
done | sort -u
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.