Come posso verificare se esiste una variabile in un elenco in BASH


138

Sto provando a scrivere uno script in bash che controlli la validità di un input dell'utente.
Voglio abbinare l'input (diciamo variabile x) a un elenco di valori validi.

quello che ho escogitato al momento è:

for item in $list
do
    if [ "$x" == "$item" ]; then
        echo "In the list"
        exit
    fi
done

La mia domanda è se esiste un modo più semplice per farlo,
qualcosa come un list.contains(x)per la maggior parte dei linguaggi di programmazione.

Aggiunta:
dire che l'elenco è:

list="11 22 33"

il mio codice farà eco al messaggio solo per quei valori poiché listviene trattato come un array e non come una stringa, tutte le manipolazioni delle stringhe verranno convalidate 1mentre vorrei che fallisse.

Risposte:


143
[[ $list =~ (^|[[:space:]])$x($|[[:space:]]) ]] && echo 'yes' || echo 'no'

o crea una funzione:

contains() {
    [[ $1 =~ (^|[[:space:]])$2($|[[:space:]]) ]] && exit(0) || exit(1)
}

per usarlo:

contains aList anItem
echo $? # 0: match, 1: failed

37
dovrebbe essere[[ $list =~ (^| )$x($| ) ]] && echo 'yes' || echo 'no'
Matvey Aksenov il

12
Può dare falsi positivi se l'input dell'utente contiene caratteri speciali espressione regolari, ad esempiox=.
Glenn Jackman

4
Questo darà senza falsi positivi / negativi: contains () { [[ "$1" =~ (^|[[:space:]])"$2"($|[[:space:]]) ]]; }.
skozin,

6
una soluzione più succinta: [[ " $list " =~ " $x " ]] && echo 'yes' || echo 'no'. è corretto supponendo che lo spazio sia il separatore e $xnon contenga spazio
Tianren Liu,

2
Penso che sia meglio usare una funzione "is in" in isIn()modo da poter scrivere prima l'elemento nei parametri. Inoltre puoi fare echoqualcosa invece di usare in exitquesto modo: [[ $2 =~ (^|[[:space:]])$1($|[[:space:]]) ]] && echo 1 || echo 0Quindi puoi usare la funzione in questo modo:result=$(isIn "-t" "-o -t 45") && echo $result
hayj

34

Matvey ha ragione, ma dovresti citare $ x e prendere in considerazione qualsiasi tipo di "spazio" (es. Nuova riga) con

[[ $list =~ (^|[[:space:]])"$x"($|[[:space:]]) ]] && echo 'yes' || echo 'no' 

quindi, ad es

# list_include_item "10 11 12" "2"
function list_include_item {
  local list="$1"
  local item="$2"
  if [[ $list =~ (^|[[:space:]])"$item"($|[[:space:]]) ]] ; then
    # yes, list include item
    result=0
  else
    result=1
  fi
  return $result
}

fine allora

`list_include_item "10 11 12" "12"`  && echo "yes" || echo "no"

o

if `list_include_item "10 11 12" "1"` ; then
  echo "yes"
else 
  echo "no"
fi

Si noti che è necessario utilizzare ""in caso di variabili:

`list_include_item "$my_list" "$my_item"`  && echo "yes" || echo "no"

3
Questa soluzione funziona anche se $itemcontiene caratteri speciali, come .(ma $listprobabilmente la variabile deve essere quotata all'interno del test). E la funzione può essere definita ancora più semplice: contains () { [[ "$1" =~ (^|[[:space:]])"$2"($|[[:space:]]) ]]; }.
skozin,

non funziona in casi come: list="aa bb xx cc ff"ex="aa bb"
creativeChips

Ciao. Sto cercando di apprendere i fondamenti di bashscript e volevo sapere come è possibile analizzare una stringa di elenco, in elenco e fare il controllo esistente in quell'elenco. Posso capire che hai diviso usando lo spazio ma non sono riuscito a comprendere appieno l'espressione. Mi può fornire le parole chiave su Google i fondamenti di questa espressione: $list =~ (^|[[:space:]])"$item"($|[[:space:]]). Oppure, se hai tempo, sarei felice di ascoltare la tua spiegazione per questa espressione. Nota: suppongo sia un'espressione regex (inizia con ^ etc) ma non so cosa significhi = ~. Quindi mi piacerebbe una spiegazione generale: P
Ali Yılmaz,

28

La soluzione più semplice di IMHO è anteporre e aggiungere la stringa originale con uno spazio e verificare con una regex con [[ ]]

haystack='foo bar'
needle='bar'

if [[ " $haystack " =~ .*\ $needle\ .* ]]; then
    ...
fi

questo non sarà falso positivo su valori con valori contenenti l'ago come sottostringa, ad es. con un pagliaio foo barbaz .

(Il concetto è rubato senza vergogna da JQuery's hasClass()-Method)


4
se il separatore non è spazio, la soluzione è più ovvia. può anche essere fatto senza regex: haystack="foo:bar"e[[ ":$haystack:" = *:$needle:* ]]
phiphi il

25

che ne dite di

echo $list | grep -w $x

è possibile controllare l'output o $? sopra la riga per prendere la decisione.

grep -w controlla su schemi di parole intere.


1
questo non convaliderebbe "val" se "valore" è valido (cioè nell'elenco) poiché è la sua sottostringa
Ofir Farchy

Sì, così come la risposta accettata. @glennjackman offre una soluzione funzionante.
f.ardelian,

3
puoi usare grep -q per rendere silenzioso grep
Amir,

questo accetterebbe anche corrispondenze parziali, che potrebbero non essere ciò che l'utente desidera
carnevale

3
@carnicer quindi usa grep -w $ x per avere una corrispondenza esatta
user1297406

18

Puoi usare (* caratteri jolly) anche al di fuori di un'istruzione case, se usi parentesi doppie:

string='My string';

if [[ $string == *My* ]]
then
echo "It's there!";
fi

3
Semplice ed elegante, +1
João Pereira

14
Questo darà falsi positivi se "My" è una sottostringa di un'altra stringa, ad esempio string = 'MyCommand string'.
Luke Lee

Vale la pena notare che i caratteri jolly ( *My*) devono trovarsi sul lato destro del test.
Jacob Vanus,

8

Se il tuo elenco di valori deve essere codificato nello script, è abbastanza semplice testarlo case. Ecco un breve esempio, che puoi adattare alle tue esigenze:

for item in $list
do
    case "$x" in
      item1|item2)
        echo "In the list"
        ;;
      not_an_item)
        echo "Error" >&2
        exit 1
        ;;
    esac
done

Se l'elenco è una variabile di matrice in fase di esecuzione, una delle altre risposte è probabilmente una soluzione migliore.


7

Se l'elenco è stato corretto nello script, mi piace quanto segue:

validate() {
    grep -F -q -x "$1" <<EOF
item 1
item 2
item 3
EOF
}

Quindi utilizzare validate "$x"per verificare se $xè consentito.

Se vuoi un one-liner e non ti interessa lo spazio bianco nei nomi degli articoli, puoi usarlo (nota -winvece di -x):

validate() { echo "11 22 33" | grep -F -q -w "$1"; }

Appunti:

  • Questo è shconforme a POSIX .
  • validatenon non accetta stringhe (rimuovere l' -xopzione per grep se si desidera che).
  • validateinterpreta il suo argomento come una stringa fissa, non un'espressione regolare (rimuovere l' -Fopzione grep se lo si desidera).

Codice di esempio per esercitare la funzione:

for x in "item 1" "item2" "item 3" "3" "*"; do
    echo -n "'$x' is "
    validate "$x" && echo "valid" || echo "invalid"
done

6

Valuta di sfruttare le chiavi degli array associativi . Presumo che questo superi sia la regex / pattern matching che il looping, anche se non l'ho profilato.

declare -A list=( [one]=1 [two]=two [three]='any non-empty value' )
for value in one two three four
do
    echo -n "$value is "
    # a missing key expands to the null string, 
    # and we've set each interesting key to a non-empty value
    [[ -z "${list[$value]}" ]] && echo -n '*not* '
    echo "a member of ( ${!list[*]} )"
done

Produzione:

one is a member of ( one two three )
two is a member of ( one two three )
three is a member of ( one two three )
four is *not* a member of ( one two three )

2
... e si può semplificare la sostituzione (e non fare affidamento su echo -n) con l'utilizzo creativo di parametri: do is="${list[$value]+is }"; echo "$value ${is:-is *not* }a member of ( ${!list[*]} )"; done.
Toby Speight,

C'è un modo semplice per creare un tale array se ho solo un (lungo) elenco come `list =" one two three xyz ... "?
Ott Toomet,

5

Trovo che sia più facile utilizzare il modulo echo $LIST | xargs -n1 echo | grep $VALUEcome illustrato di seguito:

LIST="ITEM1 ITEM2"
VALUE="ITEM1"
if [ -n "`echo $LIST | xargs -n1 echo | grep -e \"^$VALUE`$\" ]; then
    ...
fi

Funziona per un elenco separato da spazi, ma è possibile adattarlo a qualsiasi altro delimitatore (come :) procedendo come segue:

LIST="ITEM1:ITEM2"
VALUE="ITEM1"
if [ -n "`echo $LIST | sed 's|:|\\n|g' | grep -e \"^$VALUE`$\"`" ]; then
   ...
fi

Si noti che "sono necessari affinché il test funzioni.


Questo farà LIST="SOMEITEM1 ITEM2"tornare vero anche se ITEM1non è presente
Ofir Farchy,

Buona cattura, ho aggiornato l'esempio con grep -e per escludere una corrispondenza parziale.
Sébastien Pierre,

Credo che ci sia un extra `alla fine dell'istruzione" if ". La forma corretta è: [-n "` echo $ LIST | xargs -n1 echo | grep -e \ "^ $ VALUE $ \"]
Elad Tabak

3

Ho pensato di aggiungere la mia soluzione all'elenco.

# Checks if element "$1" is in array "$2"
# @NOTE:
#   Be sure that array is passed in the form:
#       "${ARR[@]}"
elementIn () {
    # shopt -s nocasematch # Can be useful to disable case-matching
    local e
    for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 0; done
    return 1
}

# Usage:
list=(11 22 33)
item=22

if elementIn "$item" "${list[@]}"; then
    echo TRUE;
else
    echo FALSE
fi
# TRUE

item=44
elementIn $item "${list[@]}" && echo TRUE || echo FALSE
# FALSE

1

Esempi

$ in_list super test me out
NO

$ in_list "super dude" test me out
NO

$ in_list "super dude" test me "super dude"
YES

# How to use in another script
if [ $(in_list $1 OPTION1 OPTION2) == "NO" ]
then
  echo "UNKNOWN type for param 1: Should be OPTION1 or OPTION2"
  exit;
fi

in_list

function show_help()
{
  IT=$(CAT <<EOF

  usage: SEARCH_FOR {ITEM1} {ITEM2} {ITEM3} ...

  e.g. 

  a b c d                    -> NO
  a b a d                    -> YES
  "test me" how "test me"    -> YES

  )
  echo "$IT"
  exit
}

if [ "$1" == "help" ]
then
  show_help
fi

if [ "$#" -eq 0 ]; then
  show_help
fi

SEARCH_FOR=$1
shift;

for ITEM in "$@"
do
  if [ "$SEARCH_FOR" == "$ITEM" ]
  then
    echo "YES"
    exit;
  fi
done

echo "NO"

1

Supponendo che la variabile TARGET possa essere solo 'binomiale' o 'regressione', seguirebbe quanto segue:

# Check for modeling types known to this script
if [ $( echo "${TARGET}" | egrep -c "^(binomial|regression)$" ) -eq 0 ]; then
    echo "This scoring program can only handle 'binomial' and 'regression' methods now." >&2
    usage
fi

È possibile aggiungere più stringhe all'elenco separandole con un | carattere (pipe).

Il vantaggio di usare egrep è che potresti facilmente aggiungere insensibilità al maiuscolo (-i) o controllare scenari più complessi con un'espressione regolare.


1

Questa è quasi la tua proposta originale ma quasi un liner. Non così complicato come le altre risposte valide, e non così a seconda delle versioni bash (può funzionare con vecchie bashes).

OK=0 ; MP_FLAVOURS="vanilla lemon hazelnut straciatella"
for FLAV in $MP_FLAVOURS ; do [ $FLAV == $FLAVOR ] && { OK=1 ; break; } ; done
[ $OK -eq 0 ] && { echo "$FLAVOR not a valid value ($MP_FLAVOURS)" ; exit 1 ; }

Immagino che la mia proposta possa ancora essere migliorata, sia in termini di lunghezza che di stile.


0

Se non è troppo lungo; puoi semplicemente metterli tra l'uguaglianza lungo un confronto OR logico in questo modo.

if [ $ITEM == "item1" -o $ITEM == "item2" -o $ITEM == "item3" ]; then
    echo In the list
fi 

Ho avuto questo esatto problema e mentre quanto sopra è brutto è più ovvio cosa sta succedendo rispetto alle altre soluzioni generalizzate.

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.