Testet, ob sich das Element in einem Array in der Bash befindet

15

Gibt es eine gute Möglichkeit zu überprüfen, ob ein Array ein Element in bash enthält (besser als eine Schleife durchzuführen)?

Gibt es alternativ eine andere Möglichkeit, um zu überprüfen, ob eine Zahl oder eine Zeichenfolge einer Reihe vordefinierter Konstanten entspricht?

Tgr
quelle

Antworten:

22

In Bash 4 können Sie assoziative Arrays verwenden:

# 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

Um das Array zunächst einzurichten, können Sie auch direkte Zuweisungen vornehmen:

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

oder so:

array=([foo]=1 [bar]=1 [baz]=1)
Dennis Williamson
quelle
Tatsächlich funktioniert der Test [[]] nicht, wenn der Wert leer ist. ZB "array ['test'] = ''". In diesem Fall existiert der Schlüssel 'test' und Sie können ihn mit $ {! Array [@]} aufgelistet sehen, aber "[[$ {array ['test']}]; echo $?" Echos 1, nicht 0.
Haridsv
1
${array[$test1]}ist einfach, hat aber ein Problem: Es funktioniert nicht, wenn Sie set -uin Ihren Skripten verwenden (was empfohlen wird), da Sie "ungebundene Variable" erhalten würden.
Tokland
@tokland: Wer empfiehlt das? Das tue ich bestimmt nicht.
Dennis Williamson
@DennisWilliamson: Ok, einige Leute empfehlen es, aber ich denke, es wäre schön, eine Lösung zu haben, die unabhängig vom Wert dieser Flags funktioniert.
Tokland
10

Es ist eine alte Frage, aber ich denke , was die einfachste Lösung schien noch nicht ist: test ${array[key]+_}. Beispiel:

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"

Ausgänge:

a is set
b is set

Um zu sehen, wie dies funktioniert, überprüfen Sie dies .

tokland
quelle
2
Das Info-Handbuch empfiehlt, dass Sie es verwenden env, um Unklarheiten in Aliasen, Progs und anderen Funktionen zu vermeiden, die möglicherweise den Namen "Test" angenommen haben. Wie oben env test ${xs[a]+_} && echo "a is set". Sie können diese Funktion auch mit doppelten Klammern erhalten, wobei derselbe Trick dann auf null prüft:[[ ! -z "${xs[b]+_}" ]] && echo "b is set"
A.Danischewski
Auch Sie können die noch einfacher[[ ${xs[b]+set} ]]
Arne L.
5

Es gibt eine Möglichkeit zu testen, ob ein Element eines assoziativen Arrays existiert (nicht gesetzt). Dies unterscheidet sich von leer:

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

Dann benutze es:

declare -A assoc
KEY="key"
isNotSet assoc[${KEY}]
if [ $? -ne 0 ]
then
  echo "${KEY} is not set."
fi
Diego F. Durán
quelle
Nur eine Anmerkung: Deklarieren Sie, dass -A nicht für Bash 3.2.39 (Debian Lenny) funktioniert, aber für Bash 4.1.5 (Debian Squeeze)
Paweł Polewicz
Assoziative Arrays wurden in Bash 4 eingeführt.
Diego F. Durán
1
beachte das if ! some_check then return 1= some_check. Also: isNotSet() { [[ ... ]] }. Überprüfen Sie meine Lösung unten, Sie können es in einem einfachen Check tun.
Tokland
3

Sie können feststellen, ob ein Eintrag vorhanden ist, indem Sie den Inhalt des Arrays an grep weiterleiten.

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

Sie können auch den Index eines Eintrags mit grep -n abrufen, der die Zeilennummer einer Übereinstimmung zurückgibt (denken Sie daran, 1 zu subtrahieren, um einen auf Null basierenden Index zu erhalten). Dies ist mit Ausnahme sehr großer Arrays relativ schnell.

# 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

Erläuterung:

  • $( ... ) entspricht der Verwendung von Backticks zum Erfassen der Ausgabe eines Befehls in eine Variable
  • printf gibt mydata ein Element pro Zeile aus
  • (alle Zitate notwendig, zusammen mit , @anstatt *. dies vermeidet Splitting „Hallo Welt“ in 2 Zeilen)
  • grepSucht nach genauem String: ^und $stimmt mit Zeilenanfang und Zeilenende überein
  • grep -n Gibt Zeile # in Form von 4 zurück: Hallo Welt
  • grep -m 1 findet nur die erste Übereinstimmung
  • cut extrahiert nur die Zeilennummer
  • 1 von der zurückgegebenen Zeilennummer abziehen.

Sie können die Subtraktion natürlich in den Befehl einklappen. Aber dann teste auf -1, ob etwas fehlt:

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

if [[ $ix == -1 ]]; then echo missing; else ... fi
  • $(( ... )) führt eine Ganzzahl-Arithmetik durch
kane
quelle
1

Ich denke nicht, dass Sie es ohne Schleife richtig machen können, es sei denn, Sie haben sehr begrenzte Daten im Array.

Hier ist eine einfache Variante, dies würde richtig sagen, dass "Super User"es in dem Array existiert. Aber es würde auch sagen, dass "uper Use"das in der Reihe ist.

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

Das Problem ist, dass es keine einfache Möglichkeit gibt, die Anker (die ich mir vorstellen kann) hinzuzufügen, außer das Array zu durchlaufen. Es sei denn, Sie können sie hinzufügen, bevor Sie sie in das Array einfügen ...

Nifle
quelle
Es ist jedoch eine gute Lösung, wenn die Konstanten alphanumerisch sind (mit grep "\b$FINDME\b"). "(^| )$FINDME(\$| )"Könnte wahrscheinlich mit nicht-alphanumerischen Konstanten arbeiten, die keine Leerzeichen haben, mit (oder so ähnlichem ... Ich habe nie erfahren können, welche Variante von Regexp grep verwendet.)
Tgr 30.11.10
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
Cong Nguyen
quelle
1
Können Sie erläutern, warum Sie diesen Ansatz vorschlagen? OP gefragt , ob es einen Weg gibt , es zu tun , ohne durch das Array Looping , das ist das, was Sie in tun in_array. Cheers
Bertieb
Nun, zumindest diese Schleife ist in einer Funktion gekapselt, die für viele Fälle (mit kleinen Datenmengen) gut genug sein kann und keine Bash 4+ erfordert. Sollte wahrscheinlich ${ARRAY[@]}verwendet werden.
Tobias