Verifica se l'elemento è nella matrice in bash


17

C'è un buon modo per verificare se un array ha un elemento in bash (meglio che fare un ciclo)?

In alternativa, c'è un altro modo per verificare se un numero o una stringa è uguale a una qualsiasi di una serie di costanti predefinite?

Risposte:


24

In Bash 4, puoi usare array associativi:

# set up array of constants
declare -A array
for constant in foo bar baz
do
    array[$constant]=1
done

# test for existence
test1="bar"
test2="xyzzy"

if [[ ${array[$test1]} ]]; then echo "Exists"; fi    # Exists
if [[ ${array[$test2]} ]]; then echo "Exists"; fi    # doesn't

Per configurare l'array inizialmente è possibile anche eseguire assegnazioni dirette:

array[foo]=1
array[bar]=1
# etc.

o in questo modo:

array=([foo]=1 [bar]=1 [baz]=1)

In realtà, il test [[]] non funziona nel caso in cui il valore sia vuoto. Ad esempio, "array ['test'] = ''". In questo caso, esiste la chiave 'test', e puoi vederlo elencato con $ {! Array [@]}, ma "[[[$ {array ['test']}]]; echo $?" echi 1, non 0.
haridsv

1
${array[$test1]}è semplice ma ha un problema: non funzionerà se lo usi set -unei tuoi script (che è consigliato), dato che otterrai "variabile non associata".
tokland

@tokland: chi lo consiglia? Certamente no.
In pausa fino a ulteriore avviso.

@DennisWilliamson: Ok, alcune persone lo raccomandano, ma penso che sarebbe bello avere una soluzione che funzioni indipendentemente dal valore di questi flag.
tokland


10

E 'una vecchia questione, ma credo che ciò che è la soluzione più semplice non è ancora apparso: test ${array[key]+_}. Esempio:

declare -A xs=([a]=1 [b]="")
test ${xs[a]+_} && echo "a is set"
test ${xs[b]+_} && echo "b is set"
test ${xs[c]+_} && echo "c is set"

Uscite:

a is set
b is set

Per vedere come funziona controlla questo .


2
Il manuale informativo ti consiglia di utilizzare envper evitare ambiguità in alias, prog e altre funzioni che potrebbero aver adottato il nome "test". Come sopra env test ${xs[a]+_} && echo "a is set". Puoi anche ottenere questa funzionalità usando le parentesi doppie, lo stesso trucco quindi controllando null:[[ ! -z "${xs[b]+_}" ]] && echo "b is set"
A.Danischewski

Inoltre puoi usare anche il più semplice[[ ${xs[b]+set} ]]
Arne L.

5

C'è un modo per testare se esiste un elemento di un array associativo (non impostato), questo è diverso da vuoto:

isNotSet() {
    if [[ ! ${!1} && ${!1-_} ]]
    then
        return 1
    fi
}

Quindi usalo:

declare -A assoc
KEY="key"
isNotSet assoc[${KEY}]
if [ $? -ne 0 ]
then
  echo "${KEY} is not set."
fi

solo una nota: dichiarare -A non funziona su bash 3.2.39 (debian lenny), ma funziona su bash 4.1.5 (debian squeeze)
Paweł Polewicz,

Le matrici associative sono state introdotte in Bash 4.
Diego F. Durán,

1
si noti che if ! some_check then return 1= some_check. Quindi: isNotSet() { [[ ... ]] }. Controlla la mia soluzione di seguito, puoi farlo con un semplice controllo.
tokland

3

Puoi vedere se è presente una voce eseguendo il piping del contenuto dell'array su grep.

 printf "%s\n" "${mydata[@]}" | grep "^${val}$"

È anche possibile ottenere l'indice di una voce con grep -n, che restituisce il numero di riga di una corrispondenza (ricordarsi di sottrarre 1 per ottenere un indice in base zero) Questo sarà ragionevolmente veloce, tranne che per array molto grandi.

# given the following data
mydata=(a b c "hello world")

for val in a c hello "hello world"
do
           # get line # of 1st matching entry
    ix=$( printf "%s\n" "${mydata[@]}" | grep -n -m 1 "^${val}$" | cut -d ":" -f1 )

    if [[ -z $ix ]]
    then
        echo $val missing
    else
         # subtract 1.  Bash arrays are zero-based, but grep -n returns 1 for 1st line, not 0 
        echo $val found at $(( ix-1 ))
    fi
done

a found at 0
c found at 2
hello missing
hello world found at 3

spiegazione:

  • $( ... ) equivale a utilizzare i backtick per acquisire l'output di un comando in una variabile
  • printf genera mydata un elemento per riga
  • (tutte le virgolette necessarie, insieme a questa @invece *.evita di dividere "ciao mondo" in 2 righe)
  • grepcerca la stringa esatta: ^e $corrisponde all'inizio e alla fine della riga
  • grep -n ritorna riga #, in forma di 4: ciao mondo
  • grep -m 1 trova solo la prima partita
  • cut estrae solo il numero di riga
  • sottrarre 1 dal numero di riga restituito.

Ovviamente puoi piegare la sottrazione nel comando. Ma quindi prova -1 per mancare:

ix=$(( $( printf "%s\n" "${mydata[@]}" | grep -n -m 1 "^${val}$" | cut -d ":" -f1 ) - 1 ))

if [[ $ix == -1 ]]; then echo missing; else ... fi
  • $(( ... )) fa l'aritmetica intera

1

Non penso che tu possa farlo correttamente senza loop a meno che tu non abbia dati molto limitati nell'array.

Ecco una semplice variante, questo direbbe correttamente che "Super User"esiste nella matrice. Ma direbbe anche che "uper Use"è nella matrice.

MyArray=('Super User' 'Stack Overflow' 'Server Fault' 'Jeff' );
FINDME="Super User"

FOUND=`echo ${MyArray[*]} | grep "$FINDME"`

if [ "${FOUND}" != "" ]; then
  echo Array contains: $FINDME
else
  echo $FINDME not found
fi

#
# If you where to add anchors < and > to the data it could work
# This would find "Super User" but not "uper Use"
#

MyArray2=('<Super User>' '<Stack Overflow>' '<Server Fault>' '<Jeff>' );

FOUND=`echo ${MyArray2[*]} | grep "<$FINDME>"`

if [ "${FOUND}" != "" ]; then
  echo Array contains: $FINDME
else
  echo $FINDME not found
fi

Il problema è che non esiste un modo semplice per aggiungere gli ancoraggi (a cui riesco a pensare) oltre a fare il loop nell'array. A meno che non sia possibile aggiungerli prima di inserirli nell'array ...


È una buona soluzione quando le costanti sono alfanumeriche, però (con grep "\b$FINDME\b"). Probabilmente potrebbe funzionare con costanti non alfanumeriche che non hanno spazi, con "(^| )$FINDME(\$| )"(o qualcosa del genere ... Non sono mai stato in grado di apprendere quale sapore utilizza regexp grep.)
Tgr

1
#!/bin/bash
function in_array {
  ARRAY=$2
  for e in ${ARRAY[*]}
  do
    if [[ "$e" == "$1" ]]
    then
      return 0
    fi
  done
  return 1
}

my_array=(Drupal Wordpress Joomla)
if in_array "Drupal" "${my_array[*]}"
  then
    echo "Found"
  else
    echo "Not found"
fi

1
Puoi approfondire il motivo per cui stai suggerendo questo approccio? OP ha chiesto se esiste un modo per farlo senza eseguire il loop nell'array , che è ciò che si sta facendo in_array. Saluti
bertieb

Bene, almeno quel loop è racchiuso in una funzione, che potrebbe essere abbastanza buono per molti casi (con piccoli set di dati) e non richiede bash 4+. Probabilmente ${ARRAY[@]}dovrebbe essere usato.
Tobias,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.