Controlla se un array Bash contiene un valore


443

In Bash, qual è il modo più semplice per verificare se un array contiene un determinato valore?

Modifica : con l'aiuto delle risposte e dei commenti, dopo alcuni test, ho trovato questo:

function contains() {
    local n=$#
    local value=${!n}
    for ((i=1;i < $#;i++)) {
        if [ "${!i}" == "${value}" ]; then
            echo "y"
            return 0
        fi
    }
    echo "n"
    return 1
}

A=("one" "two" "three four")
if [ $(contains "${A[@]}" "one") == "y" ]; then
    echo "contains one"
fi
if [ $(contains "${A[@]}" "three") == "y" ]; then
    echo "contains three"
fi

Non sono sicuro che sia la soluzione migliore, ma sembra funzionare.

Risposte:


458

Questo approccio ha il vantaggio di non dover ricorrere a tutti gli elementi (almeno non esplicitamente). Ma poiché array_to_string_internal()in array.c scorre ancora gli elementi dell'array e li concatena in una stringa, probabilmente non è più efficiente delle soluzioni di loop proposte, ma è più leggibile.

if [[ " ${array[@]} " =~ " ${value} " ]]; then
    # whatever you want to do when array contains value
fi

if [[ ! " ${array[@]} " =~ " ${value} " ]]; then
    # whatever you want to do when array doesn't contain value
fi

Si noti che nei casi in cui il valore che si sta cercando è una delle parole in un elemento dell'array con spazi, si ottengono falsi positivi. Per esempio

array=("Jack Brown")
value="Jack"

Il regex vedrà "Jack" come nell'array anche se non lo è. Quindi dovrai cambiare IFSe i caratteri di separazione sul tuo regex se vuoi ancora usare questa soluzione, in questo modo

IFS=$'\t'
array=("Jack Brown\tJack Smith")
unset IFS
value="Jack"

if [[ "\t${array[@]}\t" =~ "\t${value}\t" ]]; then
    echo "true"
else
    echo "false"
fi

Questo stamperà "falso".

Ovviamente questo può anche essere usato come una dichiarazione di prova, permettendogli di essere espresso come una riga

[[ " ${array[@]} " =~ " ${value} " ]] && echo "true" || echo "false"

1
Ho aggiunto uno spazio all'inizio della prima corrispondenza del valore regex, in modo che corrispondesse solo alla parola, non a qualcosa che termina con la parola. Funziona alla grande. Tuttavia, non capisco perché usi la seconda condizione, il primo non funzionerebbe bene da solo?
JStrahl

1
@AwQiruiGuo Non sono sicuro di seguire. Stai parlando di array con valori letterali in dollari? In tal caso, assicurati di sfuggire ai dollari nel valore che stai confrontando con le barre rovesciate.
Keegan,

10
Oneliner: [[ " ${branches[@]} " =~ " ${value} " ]] && echo "YES" || echo "NO";
ericson.cepeda,

3
Shellcheck si lamenta di questa soluzione, SC2199 e SC2076. Non ho potuto correggere gli avvisi senza interrompere la funzionalità. Qualche altra idea a parte quella di disabilitare il shellcheck per quella linea?
Ali Essam,

4
SC2076 è facile da risolvere, basta rimuovere le doppie virgolette in if. Non credo che ci sia un modo per evitare SC2199 con questo approccio. Dovresti eseguire un ciclo esplicito nell'array, come mostrato in alcune delle altre soluzioni, o ignorare l'avviso.
Keegan,

388

Di seguito è una piccola funzione per raggiungere questo obiettivo. La stringa di ricerca è il primo argomento e il resto sono gli elementi dell'array:

containsElement () {
  local e match="$1"
  shift
  for e; do [[ "$e" == "$match" ]] && return 0; done
  return 1
}

Un'esecuzione di prova di quella funzione potrebbe apparire come:

$ array=("something to search for" "a string" "test2000")
$ containsElement "a string" "${array[@]}"
$ echo $?
0
$ containsElement "blaha" "${array[@]}"
$ echo $?
1

5
Funziona bene! Devo solo ricordare di passare la matrice come con le citazioni: "${array[@]}". Altrimenti gli elementi contenenti spazi interromperanno la funzionalità.
Juve,

26
Bello. Lo chiamerei elementIn () perché controlla se il primo argomento è contenuto nel secondo. contieneElements () suona come se l'array andasse per primo. Per i neofiti come me, un esempio di come utilizzare una funzione che non scrive su stdout in un'istruzione "if" sarebbe di aiuto: if elementIn "$table" "${skip_tables[@]}" ; then echo skipping table: ${table}; fi; grazie per l'aiuto!
GlenPeterson,

5
@Bluz il costrutto && è un operatore booleano AND. L'uso di operatori booleani crea un'istruzione booleana. La logica bozzeana dice che l'intera istruzione può essere vera solo se entrambe le istruzioni prima e dopo && vengono considerate vere. Questo è usato come scorciatoia da installare e if block. Il test viene valutato e, se falso, non è necessario valutare il ritorno poiché è irrilevante per l'intera istruzione una volta che il test ha avuto esito negativo e pertanto non viene eseguito. Se il test ha esito positivo, il successo dell'istruzione booleana DEVE richiedere che il risultato del ritorno sia determinato in modo da eseguire il codice.
Peteches

4
@James per convenzione il codice di successo in bash è "0" e l'errore è tutto> = 1. Questo è il motivo per cui restituisce 0 in caso di successo. :)
tftd

11
@Stelios shiftsposta l'elenco degli argomenti di 1 a sinistra (rilasciando il primo argomento) e forsenza initerare implicitamente l'elenco degli argomenti.
Christian

58
$ myarray=(one two three)
$ case "${myarray[@]}" in  *"two"*) echo "found" ;; esac
found

69
Si noti che questo non scorre su ogni elemento dell'array separatamente ... invece concatena semplicemente l'array e corrisponde a "due" come sottostringa. Ciò potrebbe causare comportamenti indesiderati se si sta verificando se la parola esatta "due" è un elemento dell'array.
MartyMacGyver,

Pensavo che avrebbe funzionato per me nel confrontare i tipi di file, ma ho scoperto che all'aumentare dei contatori contava troppi valori ... boo!
Mike Q,

17
sbagliato! Motivo: case "${myarray[@]}" in *"t"*) echo "found" ;; esacrisultati:found
Sergej Jevsejev il

@MartyMacGyver, la prego di avere sguardo sulla mia Oltre a questa risposta stackoverflow.com/a/52414872/1619950
Aleksandr Podkutin

46
for i in "${array[@]}"
do
    if [ "$i" -eq "$yourValue" ] ; then
        echo "Found"
    fi
done

Per archi:

for i in "${array[@]}"
do
    if [ "$i" == "$yourValue" ] ; then
        echo "Found"
    fi
done

Detto questo, puoi usare un ciclo indicizzato per evitare di essere ucciso quando un elemento dell'array contiene IFS: for ((i = 0; i <$ {# array [@]}; i ++))
mkb

@Matt: devi fare attenzione ${#}quando Bash supporta array sparsi.
In pausa fino a nuovo avviso.

@Paolo, se l'array contiene uno spazio, confrontalo come una stringa. uno spazio è anche una stringa.
Scott,

@Paolo: puoi renderlo una funzione, ma le matrici non possono essere passate come argomenti, quindi dovrai trattarlo come un globale.
In pausa fino a nuovo avviso.

Dennis ha ragione. Dal manuale di riferimento di bash: "Se la parola è racchiusa tra virgolette, ... $ {name [@]} espande ogni elemento del nome in una parola separata"
mkb

37

Soluzione a una riga

printf '%s\n' ${myarray[@]} | grep -P '^mypattern$'

Spiegazione

L' printfistruzione stampa ogni elemento dell'array su una riga separata.

L' grepistruzione usa i caratteri speciali ^e $trova una riga che contiene esattamente il modello indicato come mypattern(niente di più, niente di meno).


uso

Per dirlo in una if ... thendichiarazione:

if printf '%s\n' ${myarray[@]} | grep -q -P '^mypattern$'; then
    # ...
fi

Ho aggiunto a -q bandiera grepall'espressione in modo che non stampi le corrispondenze; tratterà semplicemente l'esistenza di una partita come "vera".


Bella soluzione! Su GNU grep, c'è anche "--line-regexp" che potrebbe sostituire "-P" e ^ e $ nel modello: printf '% s \ n' $ {myarray [@]} | grep -q --line-regexp 'mypattern'
presto8

19

Se hai bisogno di prestazioni, non vuoi eseguire il ciclo dell'intero array ogni volta che esegui una ricerca.

In questo caso, è possibile creare un array associativo (tabella hash o dizionario) che rappresenta un indice di tale array. Vale a dire che mappa ogni elemento dell'array nel suo indice nell'array:

make_index () {
  local index_name=$1
  shift
  local -a value_array=("$@")
  local i
  # -A means associative array, -g means create a global variable:
  declare -g -A ${index_name}
  for i in "${!value_array[@]}"; do
    eval ${index_name}["${value_array[$i]}"]=$i
  done
}

Quindi puoi usarlo in questo modo:

myarray=('a a' 'b b' 'c c')
make_index myarray_index "${myarray[@]}"

E prova l'iscrizione in questo modo:

member="b b"
# the "|| echo NOT FOUND" below is needed if you're using "set -e"
test "${myarray_index[$member]}" && echo FOUND || echo NOT FOUND

O anche:

if [ "${myarray_index[$member]}" ]; then 
  echo FOUND
fi

Si noti che questa soluzione fa la cosa giusta anche se ci sono spazi nel valore testato o nei valori dell'array.

Come bonus, ottieni anche l'indice del valore all'interno dell'array con:

echo "<< ${myarray_index[$member]} >> is the index of $member"

+1 per l'idea che dovresti usare un array associativo. Penso che il codice per make_indexsia un po 'più elaborato a causa della direzione indiretta; avresti potuto usare un nome di array fisso con un codice molto più semplice.
musiphil,

17

Di solito uso solo:

inarray=$(echo ${haystack[@]} | grep -o "needle" | wc -w)

un valore diverso da zero indica che è stata trovata una corrispondenza.


È vero, questa è sicuramente la soluzione più semplice - a mio avviso dovrebbe essere contrassegnata la risposta. Almeno ho il mio voto! [:
ToVine

2
Non funzionerà con aghi simili. Ad esempio,haystack=(needle1 needle2); echo ${haystack[@]} | grep -o "needle" | wc -w
Keegan,

1
Verissimo. unire un delimitatore non presente in nessun elemento e aggiungerlo all'ago sarebbe di aiuto. Forse qualcosa come ... (non testato)inarray=$(printf ",%s" "${haystack[@]}") | grep -o ",needle" | wc -w)
Sean DiSanti,

2
L'uso di grep -x eviterebbe falsi positivi: inarray=$(printf ",%s" "${haystack[@]}") | grep -x "needle" | wc -l
jesjimher

Forse semplicemente inarray=$(echo " ${haystack[@]}" | grep -o " needle" | wc -w)come -x fa sì che grep tenti di far corrispondere l'intera stringa di input
MI Wright il

17

Un altro rivestimento senza una funzione:

(for e in "${array[@]}"; do [[ "$e" == "searched_item" ]] && exit 0; done) && echo "found" || echo "not found"

Grazie @Qwerty per l'heads up per quanto riguarda gli spazi!

funzione corrispondente:

find_in_array() {
  local word=$1
  shift
  for e in "$@"; do [[ "$e" == "$word" ]] && return 0; done
  return 1
}

esempio:

some_words=( these are some words )
find_in_array word "${some_words[@]}" || echo "expected missing! since words != word"

1
Perché abbiamo bisogno di una subshell qui?
codeforester

1
@codeforester questo è vecchio ... ma siccome è stato scritto ne hai bisogno per interromperlo, ecco cosa exit 0fa (si ferma al più presto se trovato).
estani,

La fine di un liner dovrebbe essere || echo not foundinvece di || not foundoppure la shell proverà ad eseguire un comando con il nome di not con argomento trovato se il valore richiesto non è nella matrice.
zoke

11
containsElement () { for e in "${@:2}"; do [[ "$e" = "$1" ]] && return 0; done; return 1; }

Ora gestisce correttamente gli array vuoti.


In che modo differisce dalla risposta di @ patrik? L'unica differenza che vedo è "$e" = "$1"(invece di "$e" == "$1") che sembra un bug.
CivFan,

1
Non è. @ patrik ha unito il mio commento nella sua risposta originale allora (patch # 4). Nota: "e" == "$1"è sintatticamente più chiaro.
Yann,

@CivFan Nella sua forma attuale, questo è più breve di quello nella risposta di Patrik, a causa dell'elegante $ {@: 2} e dell'autocontrollo $ 1. Vorrei aggiungere che la citazione non è necessaria entro [[]].
Hontvári Levente,

9

Ecco un piccolo contributo:

array=(word "two words" words)  
search_string="two"  
match=$(echo "${array[@]:0}" | grep -o $search_string)  
[[ ! -z $match ]] && echo "found !"  

Nota: in questo modo non si distingue il caso "due parole" ma questo non è richiesto nella domanda.


Questo mi ha aiutato molto. Grazie!
Ed Manet,

La domanda non dice esplicitamente che devi dare la risposta corretta, ma penso che sia implicito nella domanda ... L'array non contiene il valore "due".
tetsujin,

Quanto sopra segnalerà una corrispondenza per "rd".
Noel Yap,

6

Se vuoi fare un test rapido e sporco per vedere se vale la pena iterare sull'intero array per ottenere una corrispondenza precisa, Bash può trattare gli array come scalari. Test per una corrispondenza nello scalare, se nessuno quindi saltare il ciclo fa risparmiare tempo. Ovviamente puoi ottenere falsi positivi.

array=(word "two words" words)
if [[ ${array[@]} =~ words ]]
then
    echo "Checking"
    for element in "${array[@]}"
    do
        if [[ $element == "words" ]]
        then
            echo "Match"
        fi
    done
fi

Questo produrrà "Verifica" e "Abbinamento". Con array=(word "two words" something)esso produrrà solo "Verifica". Con array=(word "two widgets" something)non ci sarà alcun output.


Perché non sostituire semplicemente wordscon una regex ^words$che corrisponde solo all'intera stringa, che elimina completamente la necessità di controllare ogni articolo singolarmente?
Dejay Clayton,

@DejayClayton: Perché pattern='^words$'; if [[ ${array[@]} =~ $pattern ]]non corrisponderà mai poiché controlla l' intero array in una sola volta come se fosse uno scalare. I singoli controlli nella mia risposta devono essere effettuati solo se c'è un motivo per procedere in base alla corrispondenza approssimativa.
In pausa fino a nuovo avviso.

Ah, vedo cosa stai cercando di fare. Ho proposto una risposta variante più performante e sicura.
Dejay Clayton,

6

Questo funziona per me:

# traditional system call return values-- used in an `if`, this will be true when returning 0. Very Odd.
contains () {
    # odd syntax here for passing array parameters: http://stackoverflow.com/questions/8082947/how-to-pass-an-array-to-a-bash-function
    local list=$1[@]
    local elem=$2

    # echo "list" ${!list}
    # echo "elem" $elem

    for i in "${!list}"
    do
        # echo "Checking to see if" "$i" "is the same as" "${elem}"
        if [ "$i" == "${elem}" ] ; then
            # echo "$i" "was the same as" "${elem}"
            return 0
        fi
    done

    # echo "Could not find element"
    return 1
}

Esempio di chiamata:

arr=("abc" "xyz" "123")
if contains arr "abcx"; then
    echo "Yes"
else
    echo "No"
fi

5
a=(b c d)

if printf '%s\0' "${a[@]}" | grep -Fqxz c
then
  echo 'array “a” contains value “c”'
fi

Se preferisci puoi usare opzioni lunghe equivalenti:

--fixed-strings --quiet --line-regexp --null-data

1
Questo non funziona con BSD-grep su Mac, poiché non ci sono --null-data. :(
Will

4

Prendendo in prestito da Dennis Williamson 's risposta , i seguenti soluzione combina le matrici, le coperture di sicurezza citando, e le espressioni regolari per evitare la necessità di: iterare su loop; utilizzo di tubi o altri sottoprocessi; o usando utility non bash.

declare -a array=('hello, stack' one 'two words' words last)
printf -v array_str -- ',,%q' "${array[@]}"

if [[ "${array_str},," =~ ,,words,, ]]
then
   echo 'Matches'
else
   echo "Doesn't match"
fi

Il codice sopra funziona usando le espressioni regolari di Bash per confrontarsi con una versione restrittiva del contenuto dell'array. Esistono sei passaggi importanti per garantire che la corrispondenza dell'espressione regolare non possa essere ingannata da combinazioni intelligenti di valori all'interno dell'array:

  1. Costruire la stringa di confronto utilizzando Bash incorporato printfshell-citare, %q. La quotazione della shell garantirà che i caratteri speciali diventino "sicuri per la shell" essendo evitati con una barra rovesciata \.
  2. Scegli un carattere speciale da utilizzare come delimitatore di valore. Il delimitatore DEVE essere uno dei caratteri speciali che verranno sfuggiti durante l'uso %q; questo è l'unico modo per garantire che i valori all'interno dell'array non possano essere costruiti in modo intelligente per ingannare la corrispondenza delle espressioni regolari. Scelgo la virgola ,perché quel personaggio è il più sicuro se valutato o usato in modo improprio in un modo altrimenti inaspettato.
  3. Combina tutti gli elementi dell'array in una singola stringa, usando due istanze del carattere speciale per fungere da delimitatore. Usando la virgola come esempio, ho usato ,,%qcome argomento per printf. Questo è importante perché due istanze del carattere speciale possono apparire una accanto all'altra solo quando appaiono come delimitatore; tutte le altre istanze del personaggio speciale verranno salvate.
  4. Aggiungi due istanze finali del delimitatore alla stringa, per consentire le corrispondenze con l'ultimo elemento dell'array. Quindi, invece di confrontare contro ${array_str}, confronta contro ${array_str},,.
  5. Se la stringa di destinazione che stai cercando è fornita da una variabile utente, devi evitare tutte le istanze del carattere speciale con una barra rovesciata. Altrimenti, la corrispondenza delle espressioni regolari diventa vulnerabile a essere ingannata da elementi dell'array abilmente realizzati.
  6. Esegui una corrispondenza dell'espressione regolare di Bash contro la stringa.

Molto intelligente. Vedo che la maggior parte dei potenziali problemi sono evitati, ma vorrei provare per vedere se ci sono casi angolari. Inoltre, vorrei vedere un esempio di gestione del punto 5. Qualcosa come printf -v pattern ',,%q,,' "$user_input"; if [[ "${array_str},," =~ $pattern ]]forse.
In pausa fino a nuovo avviso.

case "$(printf ,,%q "${haystack[@]}"),," in (*"$(printf ,,%q,, "$needle")"*) true;; (*) false;; esac
Tino,

3

Una piccola aggiunta alla risposta di @ ghostdog74 sull'uso della caselogica per verificare che l'array contenga un valore particolare:

myarray=(one two three)
word=two
case "${myarray[@]}" in  ("$word "*|*" $word "*|*" $word") echo "found" ;; esac

O con l' extglobopzione attivata, puoi farlo in questo modo:

myarray=(one two three)
word=two
shopt -s extglob
case "${myarray[@]}" in ?(*" ")"$word"?(" "*)) echo "found" ;; esac

Inoltre possiamo farlo con una ifdichiarazione:

myarray=(one two three)
word=two
if [[ $(printf "_[%s]_" "${myarray[@]}") =~ .*_\[$word\]_.* ]]; then echo "found"; fi

2

dato:

array=("something to search for" "a string" "test2000")
elem="a string"

quindi un semplice controllo di:

if c=$'\x1E' && p="${c}${elem} ${c}" && [[ ! "${array[@]/#/${c}} ${c}" =~ $p ]]; then
  echo "$elem exists in array"
fi

dove

c is element separator
p is regex pattern

(Il motivo per assegnare p separatamente, piuttosto che usare l'espressione direttamente dentro [[]] è mantenere la compatibilità per bash 4)


amo il tuo uso della parola "semplice" qui ... 😂
Christian

2

Combinando alcune delle idee presentate qui puoi creare un elegante se statment senza loop che corrisponda esattamente alle parole .

$find="myword"
$array=(value1 value2 myword)
if [[ ! -z $(printf '%s\n' "${array[@]}" | grep -w $find) ]]; then
  echo "Array contains myword";
fi

Questo non si attiverà wordo val, solo corrispondenze di parole intere. Si interromperà se ogni valore di array contiene più parole.


1

In genere scrivo questo tipo di utility per operare sul nome della variabile, piuttosto che sul valore della variabile, principalmente perché bash non può altrimenti passare variabili per riferimento.

Ecco una versione che funziona con il nome dell'array:

function array_contains # array value
{
    [[ -n "$1" && -n "$2" ]] || {
        echo "usage: array_contains <array> <value>"
        echo "Returns 0 if array contains value, 1 otherwise"
        return 2
    }

    eval 'local values=("${'$1'[@]}")'

    local element
    for element in "${values[@]}"; do
        [[ "$element" == "$2" ]] && return 0
    done
    return 1
}

Con questo, l'esempio della domanda diventa:

array_contains A "one" && echo "contains one"

eccetera.


Qualcuno può pubblicare un esempio di questo utilizzato all'interno di un if, in particolare come si passa nell'array. Sto cercando di verificare se è stato passato un argomento allo script trattando i parametri come un array, ma non vuole funzionare. params = ("$ @") check = array_contains $ {params} 'SKIPDIRCHECK' se [[$ {check} == 1]]; allora .... Ma quando si esegue lo script con 'asas' come argomento, si continua a dire asas: comando non trovato. : /
Steve Childs

1

Utilizzando grepeprintf

Formatta ciascun membro dell'array su una nuova riga, quindi grepsulle righe.

if printf '%s\n' "${array[@]}" | grep -x -q "search string"; then echo true; else echo false; fi
esempio:
$ array=("word", "two words")
$ if printf '%s\n' "${array[@]}" | grep -x -q "two words"; then echo true; else echo false; fi
true

Si noti che ciò non ha problemi con i delimitatori e gli spazi.


1

Controllo su una riga senza "grep" e loop

if ( dlm=$'\x1F' ; IFS="$dlm" ; [[ "$dlm${array[*]}$dlm" == *"$dlm${item}$dlm"* ]] ) ; then
  echo "array contains '$item'"
else
  echo "array does not contain '$item'"
fi

Questo approccio non utilizza né programmi di utilità esterni grepné cicli.

Quello che succede qui è:

  • usiamo un matcher di substring con caratteri jolly per trovare il nostro articolo nella matrice concatenata in una stringa;
  • tagliamo possibili falsi positivi racchiudendo il nostro oggetto di ricerca tra una coppia di delimitatori;
  • usiamo un carattere non stampabile come delimitatore, per essere al sicuro;
  • otteniamo che il delimitatore venga utilizzato anche per la concatenazione di array mediante la sostituzione temporanea del IFSvalore della variabile;
  • rendiamo IFStemporanea la sostituzione di questo valore valutando la nostra espressione condizionale in una sotto-shell (all'interno di una coppia di parentesi)

Elimina dlm. Usa direttamente IFS.
Robin A. Meade,

Questa è la migliore risposta Mi è piaciuto così tanto, ho scritto una funzione usando questa tecnica .
Robin A. Meade,

1

Utilizzando l'espansione dei parametri:

$ {parametro: + parola} Se il parametro è nullo o non impostato, non viene sostituito nulla, altrimenti viene sostituita l'espansione della parola.

declare -A myarray
myarray[hello]="world"

for i in hello goodbye 123
do
  if [ ${myarray[$i]:+_} ]
  then
    echo ${!myarray[$i]} ${myarray[$i]} 
  else
    printf "there is no %s\n" $i
  fi
done

${myarray[hello]:+_}funziona alla grande per array associativi, ma non per i soliti array indicizzati. La domanda riguarda la ricerca di un valore in un array, non il controllo dell'esistenza della chiave di un array associativo.
Eric

0

Dopo aver risposto, ho letto un'altra risposta che mi è piaciuta particolarmente, ma è stata imperfetta e sottovalutata. Mi sono ispirato e qui ci sono due nuovi approcci che vedo fattibili.

array=("word" "two words") # let's look for "two words"

utilizzando grepe printf:

(printf '%s\n' "${array[@]}" | grep -x -q "two words") && <run_your_if_found_command_here>

utilizzando for:

(for e in "${array[@]}"; do [[ "$e" == "two words" ]] && exit 0; done; exit 1) && <run_your_if_found_command_here>

Per i risultati not_found aggiungere || <run_your_if_notfound_command_here>


0

Ecco la mia opinione su questo.

Preferirei non usare un bash per loop se posso evitarlo, poiché ciò richiede tempo per l'esecuzione. Se qualcosa deve essere ripetuto, lascia che sia qualcosa che è stato scritto in un linguaggio di livello inferiore rispetto a uno script di shell.

function array_contains { # arrayname value
  local -A _arr=()
  local IFS=
  eval _arr=( $(eval printf '[%q]="1"\ ' "\${$1[@]}") )
  return $(( 1 - 0${_arr[$2]} ))
}

Funziona creando una matrice associativa temporanea _arr, i cui indici sono derivati ​​dai valori della matrice di input. (Si noti che le matrici associative sono disponibili nella bash 4 e successive, quindi questa funzione non funzionerà nelle versioni precedenti di bash.) Impostiamo $IFSper evitare la divisione delle parole negli spazi bianchi.

La funzione non contiene loop espliciti, sebbene internamente passi attraverso l'array di input per popolare printf. Il formato printf utilizza %qper garantire la fuga dei dati di input in modo che possano essere tranquillamente utilizzati come chiavi dell'array.

$ a=("one two" three four)
$ array_contains a three && echo BOOYA
BOOYA
$ array_contains a two && echo FAIL
$

Si noti che tutto ciò che questa funzione utilizza è incorporato per bash, quindi non ci sono pipe esterne che ti trascinano verso il basso, anche nell'espansione del comando.

E se non ti piace usare eval... beh, sei libero di usare un altro approccio. :-)


Cosa succede se l'array contiene parentesi quadre?
gniourf_gniourf,

@gniourf_gniourf - sembra andare bene se le parentesi quadre sono bilanciate, ma posso vedere che è un problema se la tua matrice include valori con parentesi quadre sbilanciate. In tal caso, invoco le evalistruzioni alla fine della risposta. :)
ghoti,

Non è che non mi piaccia eval(non ho nulla contro di essa, a differenza della maggior parte delle persone che piangono evalè malvagia, soprattutto senza capire cosa c'è di male). Solo che il tuo comando è rotto. Forse %qinvece di %ssarebbe meglio.
gniourf_gniourf,

1
@gniourf_gniourf: intendevo solo il bit "un altro approccio" (e evalovviamente sono totalmente con te , ovviamente), ma hai perfettamente ragione, %qsembra aiutarti, senza rompere qualcos'altro che posso vedere. (Non avevo realizzato che% q sarebbe sfuggito anche alle parentesi quadre.) Un altro problema che ho visto e risolto riguardava gli spazi bianchi. Con a=(one "two " three), simile al problema di Keegan: non solo ha array_contains a "two "ottenuto un falso negativo, ma ha array_contains a twoottenuto un falso positivo. Abbastanza facile da risolvere impostando IFS.
ghoti,

Per quanto riguarda gli spazi bianchi, non è perché mancano le virgolette? si rompe anche con i personaggi glob. Penso che tu voglia questo invece:, eval _arr=( $(eval printf '[%q]="1"\ ' "\"\${$1[@]}\"") )e puoi abbandonare il local IFS=. C'è ancora un problema con i campi vuoti nell'array, poiché Bash rifiuterà di creare una chiave vuota in un array associativo. Un modo rapido e veloce per risolvere il problema è anteporre un personaggio fittizio, ad esempio x: eval _arr=( $(eval printf '[x%q]="1"\ ' "\"\${$1[@]}\"") )e return $(( 1 - 0${_arr[x$2]} )).
gniourf_gniourf,

-1

La mia versione della tecnica delle espressioni regolari che è già stata suggerita:

values=(foo bar)
requestedValue=bar

requestedValue=${requestedValue##[[:space:]]}
requestedValue=${requestedValue%%[[:space:]]}
[[ "${values[@]/#/X-}" =~ "X-${requestedValue}" ]] || echo "Unsupported value"

Quello che sta succedendo qui è che stai espandendo l'intero array di valori supportati in parole e anteponendo una stringa specifica, "X-" in questo caso, a ciascuno di essi e facendo lo stesso con il valore richiesto. Se questo è effettivamente contenuto nell'array, la stringa risultante corrisponderà al massimo a uno dei token risultanti, o nessuno al contrario. In quest'ultimo caso il || l'operatore si innesca e sai che hai a che fare con un valore non supportato. Prima di tutto ciò, il valore richiesto viene rimosso da tutti gli spazi iniziali e finali attraverso la manipolazione standard delle stringhe di shell.

È pulito ed elegante, credo, anche se non sono troppo sicuro di quanto possa essere performante se il tuo array di valori supportati è particolarmente grande.


-1

Ecco la mia opinione su questo problema. Ecco la versione breve:

function arrayContains() {
        local haystack=${!1}
        local needle="$2"
        printf "%s\n" ${haystack[@]} | grep -q "^$needle$"
}

E la versione lunga, che penso sia molto più facile per gli occhi.

# With added utility function.
function arrayToLines() {
        local array=${!1}
        printf "%s\n" ${array[@]}
}

function arrayContains() {
        local haystack=${!1}
        local needle="$2"
        arrayToLines haystack[@] | grep -q "^$needle$"
}

Esempi:

test_arr=("hello" "world")
arrayContains test_arr[@] hello; # True
arrayContains test_arr[@] world; # True
arrayContains test_arr[@] "hello world"; # False
arrayContains test_arr[@] "hell"; # False
arrayContains test_arr[@] ""; # False

Non uso bash da così tanto tempo ormai che faccio fatica a capire le risposte, o anche quello che ho scritto da solo :) Non riesco a credere che questa domanda continui a essere attiva dopo tutto questo tempo :)
Paolo Tedesco

Che dire test_arr=("hello" "world" "two words")?
Qwerty,

-1

Ho avuto il caso di dover verificare se un ID fosse contenuto in un elenco di ID generati da un altro script / comando. Per me ha funzionato come segue:

# the ID I was looking for
ID=1

# somehow generated list of IDs
LIST=$( <some script that generates lines with IDs> )
# list is curiously concatenated with a single space character
LIST=" $LIST "

# grep for exact match, boundaries are marked as space
# would therefore not reliably work for values containing a space
# return the count with "-c"
ISIN=$(echo $LIST | grep -F " $ID " -c)

# do your check (e. g. 0 for nothing found, everything greater than 0 means found)
if [ ISIN -eq 0 ]; then
    echo "not found"
fi
# etc.

Puoi anche accorciarlo / compattarlo in questo modo:

if [ $(echo " $( <script call> ) " | grep -F " $ID " -c) -eq 0 ]; then
    echo "not found"
fi

Nel mio caso, stavo eseguendo jq per filtrare alcuni JSON per un elenco di ID e in seguito ho dovuto verificare se il mio ID era in questo elenco e questo ha funzionato al meglio per me. Non funzionerà con array del tipo creati manualmente LIST=("1" "2" "4")ma con output di script separati da nuova riga.


PS .: non posso commentare una risposta perché sono relativamente nuovo ...


-2

Il codice seguente controlla se un determinato valore si trova nell'array e restituisce il suo offset in base zero:

A=("one" "two" "three four")
VALUE="two"

if [[ "$(declare -p A)" =~ '['([0-9]+)']="'$VALUE'"' ]];then
  echo "Found $VALUE at offset ${BASH_REMATCH[1]}"
else
  echo "Couldn't find $VALUE"
fi

La corrispondenza viene eseguita sui valori completi, pertanto l'impostazione di VALUE = "tre" non corrisponderebbe.


-2

Potrebbe valere la pena indagare se non vuoi iterare:

#!/bin/bash
myarray=("one" "two" "three");
wanted="two"
if `echo ${myarray[@]/"$wanted"/"WAS_FOUND"} | grep -q "WAS_FOUND" ` ; then
 echo "Value was found"
fi
exit

Snippet adattato da: http://www.thegeekstuff.com/2010/06/bash-array-tutorial/ Penso che sia abbastanza intelligente.

EDIT: Probabilmente potresti semplicemente fare:

if `echo ${myarray[@]} | grep -q "$wanted"` ; then
echo "Value was found"
fi

Quest'ultimo funziona solo se l'array contiene valori univoci. Cercare 1 in "143" darà falsi positivi.


-2

Un po 'in ritardo, ma potresti usare questo:

#!/bin/bash
# isPicture.sh

FILE=$1
FNAME=$(basename "$FILE") # Filename, without directory
EXT="${FNAME##*.}" # Extension

FORMATS=(jpeg JPEG jpg JPG png PNG gif GIF svg SVG tiff TIFF)

NOEXT=( ${FORMATS[@]/$EXT} ) # Formats without the extension of the input file

# If it is a valid extension, then it should be removed from ${NOEXT},
#+making the lengths inequal.
if ! [ ${#NOEXT[@]} != ${#FORMATS[@]} ]; then
    echo "The extension '"$EXT"' is not a valid image extension."
    exit
fi

-2

Ho pensato a questo, che risulta funzionare solo in zsh, ma penso che l'approccio generale sia carino.

arr=( "hello world" "find me" "what?" )
if [[ "${arr[@]/#%find me/}" != "${arr[@]}" ]]; then
    echo "found!"
else
    echo "not found!"
fi

Prendi il tuo schema da ciascun elemento solo se inizia ${arr[@]/#pattern/}o finisce ${arr[@]/%pattern/}con esso. Queste due sostituzioni funzionano in bash, ma entrambe ${arr[@]/#%pattern/}funzionano contemporaneamente in zsh.

Se l'array modificato è uguale all'originale, non contiene l'elemento.

Modificare:

Questo funziona in bash:

 function contains () {
        local arr=(${@:2})
        local el=$1
        local marr=(${arr[@]/#$el/})
        [[ "${#arr[@]}" != "${#marr[@]}" ]]
    }

Dopo la sostituzione, confronta la lunghezza di entrambi gli array. Se la matrice contiene l'elemento, la sostituzione lo eliminerà completamente e il conteggio differirà.

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.