GNU/Linux >> Linux Esercitazione >  >> Linux

Come posso ottenere valori univoci da un array in Bash?

Mi rendo conto che a questa domanda è già stata data una risposta, ma è apparsa piuttosto in alto nei risultati di ricerca e potrebbe aiutare qualcuno.

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

Esempio:

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

Se i tuoi elementi dell'array hanno uno spazio bianco o qualsiasi altro carattere speciale della shell (e puoi essere sicuro che non lo facciano?), allora per catturarli prima di tutto (e dovresti sempre farlo) esprimi il tuo array tra virgolette! per esempio. "${a[@]}" . Bash lo interpreterà letteralmente come "ogni elemento dell'array in un argomento separato ". All'interno di bash questo semplicemente funziona sempre, sempre.

Quindi, per ottenere un array ordinato (e univoco), dobbiamo convertirlo in un formato che l'ordinamento comprende ed essere in grado di riconvertirlo in elementi dell'array bash. Questo è il meglio che ho trovato:

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

Sfortunatamente, questo fallisce nel caso speciale dell'array vuoto, trasformando l'array vuoto in un array di 1 elemento vuoto (poiché printf aveva 0 argomenti ma continua a stampare come se avesse un argomento vuoto - vedi spiegazione). Quindi devi coglierlo in un if o qualcosa del genere.

Spiegazione:il formato %q per printf "shell sfugge" all'argomento stampato, in modo tale che bash possa recuperare in qualcosa come eval! Poiché ogni elemento viene stampato con shell escape sulla propria riga, l'unico separatore tra gli elementi è la nuova riga e l'assegnazione dell'array prende ogni riga come un elemento, analizzando i valori di escape in testo letterale.

ad esempio

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

L'eval è necessario per rimuovere l'escape da ogni valore che ritorna nell'array.


Se stai eseguendo Bash versione 4 o successiva (come dovrebbe essere il caso in qualsiasi versione moderna di Linux), puoi ottenere valori di array univoci in bash creando un nuovo array associativo che contenga ciascuno dei valori dell'array originale. Qualcosa del genere:

$ 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

Funziona perché in qualsiasi array (associativo o tradizionale, in qualsiasi lingua), ogni chiave può apparire solo una volta. Quando il for loop arriva al secondo valore di aa in a[2] , sovrascrive b[aa] originariamente impostato per a[0] .

Fare le cose in bash nativo può essere più veloce che usare pipe e strumenti esterni come sort e uniq , anche se per set di dati più grandi probabilmente vedrai prestazioni migliori se utilizzi un linguaggio più potente come awk, python, ecc.

Se ti senti sicuro, puoi evitare l'for loop usando printf la capacità di riciclare il suo formato per più argomenti, anche se questo sembra richiedere eval . (Smetti di leggere ora se ti va bene.)

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

Il motivo per cui questa soluzione richiede eval è che i valori dell'array vengono determinati prima della divisione delle parole. Ciò significa che l'output della sostituzione del comando è considerato una singola parola piuttosto che un insieme di coppie chiave=valore.

Mentre questo utilizza una subshell, utilizza solo bash builtin per elaborare i valori dell'array. Assicurati di valutare il tuo uso di eval con occhio critico. Se non sei sicuro al 100% che chepner o glenn jackman o greycat non trovino errori nel tuo codice, usa invece il ciclo for.


Un po' confuso, ma dovrebbe bastare:

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

Per salvare nuovamente i risultati univoci ordinati in un array, esegui Assegnazione array:

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

Se la tua shell supporta le stringhe (bash dovrebbe), puoi risparmiare un echo processo modificandolo in:

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

Una nota del 28 agosto 2021:

Secondo ShellCheck wiki 2207 a read -a pipe dovrebbe essere usato per evitare la divisione. Pertanto, in bash il comando sarebbe:

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

o

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

Inserimento:

ids=(aa ab aa ac aa ad)

Uscita:

aa ab ac ad

Spiegazione:

  • "${ids[@]}" - Sintassi per lavorare con gli array di shell, se usati come parte di echo o una stringa. Il @ parte significa "tutti gli elementi nell'array"
  • tr ' ' '\n' - Converti tutti gli spazi in nuove righe. Perché il tuo array è visto dalla shell come elementi su una singola riga, separati da spazi; e perché sort si aspetta che l'input sia su righe separate.
  • sort -u - ordina e conserva solo elementi univoci
  • tr '\n' ' ' - riconvertire in spazi le nuove righe che abbiamo aggiunto in precedenza.
  • $(...) - Sostituzione dei comandi
  • A parte:tr ' ' '\n' <<< "${ids[@]}" è un modo più efficiente di fare:echo "${ids[@]}" | tr ' ' '\n'

Linux
  1. Come creare una matrice di elementi unici da una stringa/array in Bash?

  2. Ottieni tutti i file tranne i file nell'array - Bash?

  3. Come verificare se Bash può stampare i colori?

  4. Come ottenere netmask da bash?

  5. come spostare il valore dell'array in bash

Bash break:come uscire da un loop

Come posso escludere una directory dal comando ls

Come posso eseguire un eseguibile Windows da WSL (Ubuntu) Bash

Come posso ottenere l'ultimo numero dalla stringa in bash?

Come posso filtrare i risultati univoci dall'output di grep?

Come posso ottenere la lunghezza di un file video dalla console?