it-swarm.com.de

Prüfen Sie, ob ein Bash-Array einen Wert enthält

Wie kann man in Bash am einfachsten testen, ob ein Array einen bestimmten Wert enthält? 

Edit : Mit Hilfe der Antworten und der Kommentare, nach ein paar Tests, kam ich dazu:

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

Ich bin nicht sicher, ob es die beste Lösung ist, aber es scheint zu funktionieren.

308
Paolo Tedesco

Es gibt Beispielcode, der zeigt, wie ein Teilstring aus einem Array ersetzt wird. Sie können eine Kopie des Arrays erstellen und versuchen, den Zielwert aus der Kopie zu entfernen. Wenn die Kopie und das Original dann unterschiedlich sind, ist der Zielwert in der Originalzeichenfolge vorhanden.

Die unkomplizierte (aber möglicherweise zeitaufwendigere) Lösung besteht darin, einfach das gesamte Array zu durchlaufen und jedes Element einzeln zu prüfen. Dies ist, was ich normalerweise mache, weil es einfach zu implementieren ist und Sie es in eine Funktion einschließen können (siehe diese Informationen zum Übergeben eines Arrays an eine Funktion ).

8
bta

Nachfolgend finden Sie eine kleine Funktion, um dies zu erreichen. Die Suchzeichenfolge ist das erste Argument und der Rest sind die Array-Elemente:

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

Ein Testlauf dieser Funktion könnte folgendermaßen aussehen:

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

Dieser Ansatz hat den Vorteil, dass nicht alle Elemente überlaufen werden müssen (zumindest nicht explizit). Da array_to_string_internal() in array.c noch Array-Elemente durchläuft und diese zu einem String verkettet, ist es wahrscheinlich nicht effizienter als die vorgeschlagenen Looping-Lösungen, aber lesbarer.

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

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

Beachten Sie, dass in den Fällen, in denen der Wert, den Sie suchen, eines der Wörter in einem Array-Element mit Leerzeichen ist, falsch positive Ergebnisse angezeigt werden. Zum Beispiel

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

Der Regex sieht Jack als im Array befindlich, obwohl dies nicht der Fall ist. Sie müssen also IFS und die Trennzeichen in Ihrem regulären Ausdruck ändern, wenn Sie diese Lösung weiterhin verwenden möchten

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

value="Jack Smith"

if [[ "\t${array[@]}\t" =~ "\t${value}\t" ]]; then
    echo "yep, it's there"
fi
294
Keegan
$ myarray=(one two three)
$ case "${myarray[@]}" in  *"two"*) echo "found" ;; esac
found
57
ghostdog74
for i in "${array[@]}"
do
    if [ "$i" -eq "$yourValue" ] ; then
        echo "Found"
    fi
done

Für Saiten:

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

Wenn Sie Leistung benötigen, möchten Sie nicht jedes Mal Ihr gesamtes Array durchlaufen, wenn Sie suchen.

In diesem Fall können Sie ein assoziatives Array (Hash-Tabelle oder Wörterbuch) erstellen, das einen Index dieses Arrays darstellt. Das heißt Es ordnet jedes Array-Element seinem Index im Array zu:

make_index () {
  local index_name=$1
  shift
  local -a value_array=("[email protected]")
  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
}

Dann kannst du es so benutzen:

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

Und teste die Mitgliedschaft wie folgt:

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

Oder auch:

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

Beachten Sie, dass diese Lösung das Richtige tut, auch wenn der getestete Wert oder die Array-Werte Leerzeichen enthalten.

Als Bonus erhalten Sie auch den Index des Werts innerhalb des Arrays mit:

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

Ich verwende normalerweise nur:

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

ein Wert ungleich Null zeigt an, dass eine Übereinstimmung gefunden wurde.

15
Sean DiSanti

Ein weiterer Liner ohne Funktion:

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

Danke @Qwerty für das Heads-Up bezüglich Leerzeichen!

entsprechende Funktion:

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

beispiel:

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

Hier ist ein kleiner Beitrag:

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

Hinweis: Auf diese Weise wird der Fall "zwei Wörter" nicht unterschieden, dies ist jedoch in der Frage nicht erforderlich.

10
hornetbzz

Einzeilige Lösung

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

Erklärung

Die Anweisung printf druckt jedes Element des Arrays in einer separaten Zeile.

Die Anweisung grep verwendet die Sonderzeichen ^ und $, um eine Zeile zu finden, die genau das als mypattern angegebene Muster enthält (nicht mehr und nicht weniger).


Verwendungszweck

Um dies in eine if ... then-Anweisung zu bringen:

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

Ich habe ein -q-Flag zum grep-Ausdruck hinzugefügt, damit keine Übereinstimmungen gedruckt werden. es wird nur die Existenz eines Matches als "wahr" behandeln.

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

Jetzt behandelt leere Arrays korrekt.

9
Yann

Wenn Sie einen schnellen und schmutzigen Test durchführen möchten, um zu sehen, ob es sich lohnt, das gesamte Array zu durchlaufen, um eine genaue Übereinstimmung zu erhalten, kann Bash Arrays wie Skalare behandeln. Prüfen Sie, ob eine Übereinstimmung im Skalar besteht, wenn keiner das Überspringen der Schleife Zeit spart. Offensichtlich können Sie Fehlalarme erhalten.

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

Dies gibt "Checking" und "Match" aus. Bei array=(Word "two words" something) wird nur "Checking" ausgegeben. Bei array=(Word "two widgets" something) erfolgt keine Ausgabe.

5
a=(b c d)

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

Wenn Sie möchten, können Sie gleichwertige lange Optionen verwenden:

--fixed-strings --quiet --line-regexp --null-data
5
Steven Penny

Das funktioniert für mich:

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

Beispielanruf:

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

gegeben :

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

dann eine einfache Überprüfung von:

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

woher

c is element separator
p is regex pattern

(Der Grund für die separate Zuweisung von p ist, anstatt den Ausdruck direkt in [[]] zu verwenden, die Kompatibilität für bash 4 zu gewährleisten.)

3
Beorn Harris

grep und printf verwenden

Formatieren Sie jedes Arraymitglied in einer neuen Zeile, und grep die Zeilen. 

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

Beachten Sie, dass dies keine Probleme mit Abständen und Leerzeichen hat.

2
Qwerty

Die folgende Lösung leiht sich von Dennis Williamson s answer und kombiniert Arrays, Shell-sicheres Zitieren und reguläre Ausdrücke, um die Notwendigkeit zu vermeiden: mit Rohren oder anderen Teilprozessen; oder Verwenden von Nicht-Bash-Dienstprogrammen.

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

Der obige Code funktioniert, indem reguläre Ausdrücke von Bash verwendet werden, um mit einer stringifizierten Version des Array-Inhalts abzugleichen. Es gibt sechs wichtige Schritte, um sicherzustellen, dass die Übereinstimmung des regulären Ausdrucks nicht durch clevere Kombinationen von Werten innerhalb des Arrays getäuscht werden kann:

  1. Konstruieren Sie die Vergleichszeichenfolge mithilfe der integrierten printf-Shell-Anführungszeichen von Bash, %q. Shell-Anführungszeichen sorgen dafür, dass Sonderzeichen "Shell-sicher" werden, indem sie mit dem Backslash \ maskiert werden.
  2. Wählen Sie ein Sonderzeichen aus, das als Wertbegrenzer dienen soll. Das Trennzeichen muss ein Sonderzeichen sein, das bei Verwendung von %q mit Escapezeichen versehen wird. Nur so kann sichergestellt werden, dass Werte innerhalb des Arrays nicht auf clevere Art und Weise konstruiert werden können, um die Übereinstimmung des regulären Ausdrucks zu täuschen. Ich wähle das Komma ,, weil dieses Zeichen am sichersten ist, wenn es ausgewertet oder auf andere Weise unerwartet missbraucht wird.
  3. Kombinieren Sie alle Array-Elemente in einer einzigen Zeichenfolge. Verwenden Sie two -Instanzen des Sonderzeichens, um als Trennzeichen zu dienen. Am Beispiel des Kommas habe ich ,,%q als Argument für printf verwendet. Dies ist wichtig, da zwei Instanzen des Sonderzeichens nur nebeneinander angezeigt werden können, wenn sie als Trennzeichen erscheinen. Alle anderen Instanzen des Sonderzeichens werden geschützt.
  4. Hängen Sie zwei nachgestellte Instanzen des Trennzeichens an die Zeichenfolge an, um Übereinstimmungen mit dem letzten Element des Arrays zu ermöglichen. Vergleichen Sie also nicht mit ${array_str}, sondern mit ${array_str},,.
  5. Wenn die Zielzeichenfolge, nach der Sie suchen, von einer Benutzervariable bereitgestellt wird, müssen Sie müssen alle Instanzen des Sonderzeichens mit einem umgekehrten Schrägstrich (Escape) versehen. Andernfalls wird die Übereinstimmung mit regulären Ausdrücken dadurch gefährdet, dass sie von geschickt konstruierten Array-Elementen getäuscht wird.
  6. Führen Sie einen regulären Ausdruck von Bash mit der Zeichenfolge durch.
2
Dejay Clayton

Im Allgemeinen schreibe ich diese Art von Dienstprogrammen, um mit dem Namen der Variablen und nicht mit dem Variablenwert zu arbeiten, vor allem, weil bash Variablen sonst nicht als Referenz übergeben kann.

Hier ist eine Version, die mit dem Namen des Arrays funktioniert:

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
}

Damit wird das Fragenbeispiel:

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

usw.

2
Barry Kelly

Nachdem ich geantwortet hatte, las ich eine andere Antwort, die ich besonders mochte, aber sie war fehlerhaft und wurde abgelehnt. Ich wurde inspiriert und hier sind zwei neue Ansätze, die ich für möglich halte.

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

mit grep und printf:

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

mit for:

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

Für not_found Ergebnisse addiere || <run_your_if_notfound_command_here>

1
Qwerty

Hier ist meine Meinung dazu.

Ich würde lieber keine bash for loop verwenden, wenn ich es vermeiden kann, da dies Zeit braucht, um ausgeführt zu werden. Wenn etwas schleifen muss, sei es etwas, das in einer niedrigeren Sprache geschrieben wurde als ein Shell-Skript.

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

Dies geschieht durch Erstellen eines temporären assoziativen Arrays _arr, dessen Indizes von den Werten des Eingabearrays abgeleitet werden. (Beachten Sie, dass assoziative Arrays in bash 4 und höher verfügbar sind, sodass diese Funktion in früheren bash-Versionen nicht funktioniert.) Wir setzen $IFS, um das Aufteilen von Wörtern auf Whitespace zu vermeiden.

Die Funktion enthält keine expliziten Schleifen, obwohl intern Schritte durch das Eingabearray geleitet werden, um printf aufzufüllen. Das printf-Format verwendet %q, um sicherzustellen, dass Eingabedaten mit Escape-Zeichen versehen werden, sodass sie sicher als Array-Schlüssel verwendet werden können.

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

Beachten Sie, dass alles, was diese Funktion verwendet, ein integriertes Bash ist, sodass Sie selbst in der Befehlserweiterung keine externen Pipes nach unten ziehen.

Und wenn Sie eval nicht mögen, können Sie einen anderen Ansatz wählen. :-)

1
ghoti

Wenn Sie einige der hier vorgestellten Ideen kombinieren, können Sie eine elegante, wenn auch ohne Schleifen versehene Statmentierung erstellen, die exakte Wörter mit übereinstimmt.

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

Dies wird nicht bei Word oder val ausgelöst, sondern nur bei ganzen Word-Übereinstimmungen. Es zerbricht, wenn jeder Feldwert mehrere Wörter enthält.

1
Ecker00

Ich hatte den Fall, dass ich überprüfen musste, ob eine ID in einer Liste von IDs enthalten war, die von einem anderen Skript/Befehl generiert wurden .. __ Für mich funktionierte Folgendes:

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

Sie könnten es auch so verkürzen/komprimieren:

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

In meinem Fall habe ich jq ausgeführt, um einige JSONs nach einer Liste von IDs zu filtern, und musste später überprüfen, ob meine ID in dieser Liste enthalten war, und dies funktionierte am besten für mich .. __ Sie funktioniert nicht für manuell erstellte Arrays von Geben Sie LIST=("1" "2" "4") ein, aber für mit Zeilenumbruch getrennte Skriptausgabe.


PS .: konnte keine Antwort kommentieren, da ich relativ neu bin ...

0
E. Körner

Eine kleine Ergänzung der Antwort von @ ghostdog74 zur Verwendung von case logic zur Überprüfung, dass das Array einen bestimmten Wert enthält:

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

Oder mit aktivierter Option extglob können Sie dies folgendermaßen tun:

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

Das können wir auch mit der if-Anweisung machen:

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

Hier ist meine Meinung zu diesem Problem. Hier ist die kurze Version:

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

Und die lange Version, von der ich denke, dass sie viel augenfreundlicher ist.

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

Beispiele:

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
0
robert