Wie überprüfe ich, ob eine Variable in einer Liste in BASH vorhanden ist?

137

Ich versuche, ein Skript in Bash zu schreiben, das die Gültigkeit einer Benutzereingabe überprüft.
Ich möchte die Eingabe (z. B. Variable x) mit einer Liste gültiger Werte abgleichen.

Was ich mir im Moment ausgedacht habe, ist:

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

Meine Frage ist, ob es einen einfacheren Weg gibt, so
etwas wie einen list.contains(x)für die meisten Programmiersprachen.

Ergänzung:
Sagen Sie Liste ist:

list="11 22 33"

Mein Code gibt die Nachricht nur für diese Werte wieder, da listsie als Array und nicht als Zeichenfolge behandelt werden. Alle Zeichenfolgenmanipulationen werden überprüft, 1solange ich möchte, dass sie fehlschlagen.

Ofir Farchy
quelle

Antworten:

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

oder erstellen Sie eine Funktion:

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

um es zu benutzen:

contains aList anItem
echo $? # 0: match, 1: failed
chemila
quelle
37
sollte sein[[ $list =~ (^| )$x($| ) ]] && echo 'yes' || echo 'no'
Matvey Aksenov
12
Geben kann falsch positiv , wenn die Benutzereingabe für reguläre Ausdrücke Sonderzeichen, zum Beispiel enthältx=.
glenn Jackman
4
Dies ergibt keine falsch positiven / negativen Ergebnisse : contains () { [[ "$1" =~ (^|[[:space:]])"$2"($|[[:space:]]) ]]; }.
Skozin
6
eine prägnantere Lösung : [[ " $list " =~ " $x " ]] && echo 'yes' || echo 'no'. Es ist richtig, wenn Leerzeichen das Trennzeichen sind und $xkein Leerzeichen enthalten
Tianren Liu
2
Ich denke, es ist besser, eine "is in" -Funktion zu verwenden, isIn()damit Sie das Element zuerst in Parameter schreiben können. Sie können auch echoetwas anstatt exitwie [[ $2 =~ (^|[[:space:]])$1($|[[:space:]]) ]] && echo 1 || echo 0result=$(isIn "-t" "-o -t 45") && echo $result
folgt verwenden
34

Matvey hat recht, aber Sie sollten $ x zitieren und jede Art von "Leerzeichen" (z. B. neue Zeile) mit berücksichtigen

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

also, dh

# 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
}

dann ende

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

oder

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

Beachten Sie, dass Sie ""bei Variablen Folgendes verwenden müssen :

`list_include_item "$my_list" "$my_item"`  && echo "yes" || echo "no"
Oriettaxx
quelle
3
Diese Lösung funktioniert auch dann, wenn sie $itemspezielle Zeichen enthält, wie .(aber die $listVariable muss wahrscheinlich im Test in Anführungszeichen gesetzt werden). Und die Funktion kann noch einfacher definiert werden : contains () { [[ "$1" =~ (^|[[:space:]])"$2"($|[[:space:]]) ]]; }.
Skozin
funktioniert nicht in Fällen wie: list="aa bb xx cc ff"undx="aa bb"
creativeChips
Hallo. Ich versuche, die Grundlagen von Bashscript zu lernen, und ich wollte wissen, wie Sie eine Liste von Listen in Listen analysieren können, und Sie führen die vorhandene Überprüfung in dieser Liste durch. Ich kann verstehen, dass Sie sich mit Leerzeichen teilen, aber ich konnte den Ausdruck nicht vollständig verstehen. Können Sie mir die Keywords für Google die Grundlagen dieses Ausdrucks geben : $list =~ (^|[[:space:]])"$item"($|[[:space:]]). Oder wenn Sie Zeit haben, würde ich mich über Ihre Erklärung für diesen Ausdruck freuen. Hinweis: Ich denke, es ist ein regulärer Ausdruck (beginnt mit ^ usw.), aber ich weiß nicht, was = ~ bedeutet. Also ich würde eine allgemeine Erklärung lieben: P
Ali Yılmaz
28

IMHO einfachste Lösung ist es, die ursprüngliche Zeichenfolge mit einem Leerzeichen voranzustellen und anzuhängen und mit einem regulären Ausdruck mit zu vergleichen [[ ]]

haystack='foo bar'
needle='bar'

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

Dies ist bei Werten mit Werten, die die Nadel als Teilzeichenfolge enthalten, z foo barbaz. B. mit einem Heuhaufen, nicht falsch positiv .

(Das Konzept wird schamlos von JQuery's hasClass()-Method gestohlen )

Blaimi
quelle
4
Wenn der Separator kein Leerzeichen ist, ist die Lösung offensichtlicher. es kann auch ohne Regex gemacht werden: haystack="foo:bar"und[[ ":$haystack:" = *:$needle:* ]]
Phiphi
25

wie wäre es mit

echo $list | grep -w $x

Sie können entweder die Ausgabe oder die $?obige Zeile überprüfen , um die Entscheidung zu treffen.

grep -w prüft ganze Wortmuster.

Kent
quelle
1
würde dies nicht "val" validieren, wenn "value" gültig ist (dh in der Liste), da es seine
Teilzeichenfolge
Ja, genau wie die akzeptierte Antwort. @glennjackman gibt eine funktionierende Lösung.
f.ardelian
3
Sie können grep -q verwenden, um grep ruhig zu machen
Amir
Dies würde auch Teilübereinstimmungen akzeptieren, die möglicherweise nicht den
Wünschen
3
@carnicer dann verwenden Sie einfach grep -w $ x, um eine genaue Übereinstimmung zu haben
user1297406
18

Sie können (* Platzhalter) auch außerhalb einer case-Anweisung verwenden, wenn Sie doppelte Klammern verwenden:

string='My string';

if [[ $string == *My* ]]
then
echo "It's there!";
fi
Mithun Sasidharan
quelle
3
Einfach und elegant, +1
João Pereira
14
Dies gibt falsch positiv aus, wenn "My" eine Teilzeichenfolge einer anderen Zeichenfolge ist, z. B. string = 'MyCommand string'.
Luke Lee
Es ist erwähnenswert, dass sich die Platzhalter ( *My*) auf der rechten Seite des Tests befinden müssen.
Jacob Vanus
8

Wenn Ihre Werteliste im Skript fest codiert werden soll, ist das Testen ziemlich einfach case. Hier ist ein kurzes Beispiel, das Sie an Ihre Anforderungen anpassen können:

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

Wenn die Liste zur Laufzeit eine Array-Variable ist, passt wahrscheinlich eine der anderen Antworten besser.

Toby Speight
quelle
7

Wenn die Liste im Skript festgelegt ist, gefällt mir Folgendes am besten:

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

Dann verwenden Sie validate "$x", um zu testen, ob$x zulässig ist.

Wenn Sie einen Einzeiler möchten und sich nicht für Leerzeichen in Elementnamen interessieren, können Sie dies verwenden (Hinweis -wanstelle von -x):

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

Anmerkungen:

  • Dies ist POSIX- shkonform.
  • validateist nicht Substrings akzeptieren (die entfernen -xOption grep , wenn Sie das wollen).
  • validateinterpretiert sein Argument als feste Zeichenfolge, nicht als regulären Ausdruck (entfernen Sie die -FOption zu grep, wenn Sie dies möchten).

Beispielcode zur Ausübung der Funktion:

for x in "item 1" "item2" "item 3" "3" "*"; do
    echo -n "'$x' is "
    validate "$x" && echo "valid" || echo "invalid"
done
Søren Løvborg
quelle
6

Ziehen Sie in Betracht, die Schlüssel von assoziativen Arrays auszunutzen . Ich würde annehmen, dass dies sowohl Regex / Pattern Matching als auch Looping übertrifft, obwohl ich es nicht profiliert habe.

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

Ausgabe:

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 )
RubyTuesdayDONO
quelle
2
... und Sie können dieses Ersetzen echo -ndurch kreative Verwendung von Parametern vereinfachen (und sich nicht darauf verlassen ) : do is="${list[$value]+is }"; echo "$value ${is:-is *not* }a member of ( ${!list[*]} )"; done.
Toby Speight
Gibt es eine einfache Möglichkeit, ein solches Array zu erstellen, wenn ich nur eine (lange) Liste wie `list =" eins zwei drei xyz ... "habe?
Ott Toomet
5

Ich finde es einfacher, das echo $LIST | xargs -n1 echo | grep $VALUEunten abgebildete Formular zu verwenden :

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

Dies funktioniert für eine durch Leerzeichen getrennte Liste. Sie können sie jedoch wie :folgt an ein anderes Trennzeichen (wie z. B. ) anpassen :

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

Beachten Sie, dass die "erforderlich sind, damit der Test funktioniert.

Sébastien Pierre
quelle
Dies wird dazu führen LIST="SOMEITEM1 ITEM2", dass true zurückgegeben wird, obwohl ITEM1es nicht enthalten ist
Ofir Farchy
Guter Fang, ich habe das Beispiel mit grep -e aktualisiert, um eine teilweise Übereinstimmung auszuschließen.
Sébastien Pierre
Ich glaube, am Ende der "if" -Anweisung steht ein zusätzliches ". Richtige Form ist: [-n "` echo $ LIST | xargs -n1 echo | grep -e \ "^ $ VALUE $ \"]
Elad Tabak
3

Ich dachte, ich würde meine Lösung zur Liste hinzufügen.

# 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
SamWN
quelle
1

Beispiele

$ 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"
Brad Parks
quelle
1

Angenommen, die TARGET-Variable kann nur "Binomial" oder "Regression" sein, dann würde Folgendes reichen:

# 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

Sie können der Liste weitere Zeichenfolgen hinzufügen, indem Sie sie durch ein | trennen (Rohr-) Charakter.

Der Vorteil der Verwendung von egrep besteht darin, dass Sie leicht die Groß- und Kleinschreibung (-i) hinzufügen oder komplexere Szenarien mit einem regulären Ausdruck überprüfen können.

Tagar
quelle
1

Dies ist fast Ihr ursprünglicher Vorschlag, aber fast ein 1-Liner. Nicht so kompliziert wie andere gültige Antworten und nicht so abhängig von den Bash-Versionen (kann mit alten Bashes funktionieren).

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 ; }

Ich denke, mein Vorschlag kann sowohl in der Länge als auch im Stil noch verbessert werden.

Karneval
quelle
0

Wenn es nicht zu lang ist; Sie können sie einfach zwischen Gleichheit entlang eines logischen ODER-Vergleichs wie folgt anordnen.

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

Ich hatte genau dieses Problem und obwohl das oben Gesagte hässlich ist, ist es offensichtlicher, was vor sich geht als die anderen verallgemeinerten Lösungen.

Kelly Trinh
quelle