GNU/Linux >> Znalost Linux >  >> Linux

Jak mohu získat jedinečné hodnoty z pole v Bash?

Uvědomuji si, že toto už bylo zodpovězeno, ale ve výsledcích vyhledávání se to objevilo dost vysoko a někomu to může pomoci.

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

Příklad:

~> 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
~>

Pokud vaše prvky pole obsahují mezery nebo jakýkoli jiný speciální znak shellu (a můžete si být jisti, že ne?), pak abyste je zachytili jako první (a měli byste to udělat vždy), vyjádřete pole ve dvojitých uvozovkách! např. "${a[@]}" . Bash to doslova interpretuje jako „každý prvek pole v samostatném argumentu ". V rámci bash to prostě funguje vždy, vždy."

Poté, abychom získali setříděné (a jedinečné) pole, musíme je převést do formátu, kterému řazení rozumí, a být schopni jej převést zpět na prvky pole bash. Tohle je to nejlepší, co jsem vymyslel:

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

Bohužel to selže ve speciálním případě prázdného pole, kdy se prázdné pole změní na pole 1 prázdného prvku (protože printf měl 0 argumentů, ale stále se tiskne, jako by měl jeden prázdný argument - viz vysvětlení). Takže to musíte zachytit v if nebo tak.

Vysvětlení:Formát %q pro printf "unikne" vytištěnému argumentu přesně takovým způsobem, aby se bash mohl obnovit v něčem jako eval! Protože je každý prvek vytištěn, shell unikal na svém vlastním řádku, jediným oddělovačem mezi prvky je nový řádek a přiřazení pole vezme každý řádek jako prvek a analyzuje uniklé hodnoty do doslovného textu.

např.

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

Hodnota eval je nezbytná k odstranění escapování z každé hodnoty vracející se zpět do pole.


Pokud používáte Bash verze 4 nebo vyšší (což by měl být případ jakékoli moderní verze Linuxu), můžete získat jedinečné hodnoty pole v bash vytvořením nového asociativního pole, které obsahuje každou z hodnot původního pole. Něco jako toto:

$ 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 funguje, protože v jakémkoli poli (asociativním nebo tradičním, v jakémkoli jazyce) se každý klíč může objevit pouze jednou. Když for smyčka dorazí na druhou hodnotu aa v a[2] , přepíše b[aa] který byl původně nastaven na a[0] .

Dělání věcí v nativním bash může být rychlejší než používání kanálů a externích nástrojů, jako je sort a uniq , i když u větších datových sad pravděpodobně zaznamenáte lepší výkon, pokud použijete výkonnější jazyk jako awk, python atd.

Pokud se cítíte sebejistě, můžete se vyhnout for smyčky pomocí printf schopnost recyklovat svůj formát pro více argumentů, i když se zdá, že to vyžaduje eval . (Přestaňte číst, pokud jste s tím v pohodě.)

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

Důvod, proč toto řešení vyžaduje eval je, že hodnoty pole jsou určeny před rozdělením slov. To znamená, že výstup substituce příkazu je považován za jedno slovo spíše než sadu párů klíč=hodnota.

I když to používá subshell, ke zpracování hodnot pole používá pouze vestavěné bash. Nezapomeňte vyhodnotit své použití eval kritickým okem. Pokud si nejste 100% jisti, že chepner, glenn jackman nebo greycat nenajdou žádnou chybu ve vašem kódu, použijte místo toho smyčku for.


Trochu otřepané, ale mělo by to stačit:

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

Chcete-li uložit seřazené jedinečné výsledky zpět do pole, proveďte Array Association:

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

Pokud váš shell podporuje herestring (bash měli), můžete ušetřit echo proces jeho změnou na:

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

Poznámka k 28. srpnu 2021:

Podle ShellCheck wiki 2207 a read -a potrubí by mělo být použito, aby se zabránilo rozdělení. V bash by tedy příkaz byl:

IFS=" " read -r -a ids <<< "$(echo "${ids[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ')"

nebo

IFS=" " read -r -a ids <<< "$(tr ' ' '\n' <<< "${ids[@]}" | sort -u | tr '\n' ' ')"

Vstup:

ids=(aa ab aa ac aa ad)

Výstup:

aa ab ac ad

Vysvětlení:

  • "${ids[@]}" - Syntaxe pro práci s poli shellu, ať už se používá jako součást echo nebo herestring. @ část znamená "všechny prvky v poli"
  • tr ' ' '\n' - Převést všechny mezery na nové řádky. Protože vaše pole vidí shell jako prvky na jednom řádku, oddělené mezerami; a protože řazení očekává, že vstup bude na samostatných řádcích.
  • sort -u - třídit a uchovávat pouze jedinečné prvky
  • tr '\n' ' ' - převést nové řádky, které jsme přidali dříve, zpět na mezery.
  • $(...) - Náhrada příkazů
  • Na stranu:tr ' ' '\n' <<< "${ids[@]}" je efektivnější způsob:echo "${ids[@]}" | tr ' ' '\n'

Linux
  1. Jak vytvořit pole jedinečných prvků ze řetězce/pole v Bash?

  2. Získat všechny soubory kromě souborů v poli – Bash?

  3. Jak zkontrolovat, zda Bash může tisknout barvy?

  1. Jak získat název hostitele z IP (Linux)?

  2. Jak mohu *pouze* získat počet bajtů dostupných na disku v bash?

  3. Jak mohu spustit spustitelný soubor Windows z WSL (Ubuntu) Bash

  1. Jak deklarovat 2D pole v bash

  2. Jak mohu získat poslední číslo z řetězce v bash?

  3. Jak mohu odfiltrovat jedinečné výsledky z výstupu grep?