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

442

Was ist in Bash der einfachste Weg, um zu testen, ob ein Array einen bestimmten Wert enthält?

Bearbeiten : Mit Hilfe der Antworten und Kommentare habe ich nach einigen Tests Folgendes gefunden:

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.

Paolo Tedesco
quelle

Antworten:

457

Dieser Ansatz hat den Vorteil, dass nicht alle Elemente durchlaufen werden müssen (zumindest nicht explizit). Da array_to_string_internal()in array.c immer noch Array-Elemente durchlaufen und zu einer Zeichenfolge verkettet werden, ist es wahrscheinlich nicht effizienter als die vorgeschlagenen Schleifenlösungen, aber besser lesbar.

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

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

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

Der Regex sieht "Jack" im Array, obwohl dies nicht der Fall ist. Sie müssen also IFSdie Trennzeichen in Ihrer Regex ändern, wenn Sie diese Lösung weiterhin verwenden möchten

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

Dies gibt "false" aus.

Offensichtlich kann dies auch als Testaussage verwendet werden, so dass es als Einzeiler ausgedrückt werden kann

[[ " ${array[@]} " =~ " ${value} " ]] && echo "true" || echo "false"
Keegan
quelle
1
Ich habe zu Beginn der ersten Regex-Wertübereinstimmung ein Leerzeichen hinzugefügt, damit es nur mit dem Wort übereinstimmt, nicht mit etwas, das mit dem Wort endet. Funktioniert super. Wie auch immer, ich verstehe nicht, warum Sie die zweite Bedingung verwenden, würde die erste nicht alleine funktionieren?
JStrahl
1
@AwQiruiGuo Ich bin nicht sicher, ob ich folge. Sprechen Sie über Arrays mit Dollar-Literalen? Wenn ja, stellen Sie einfach sicher, dass Sie den Dollar in dem Wert, mit dem Sie übereinstimmen, mit Backslashes vermeiden.
Keegan
10
Oneliner: [[ " ${branches[@]} " =~ " ${value} " ]] && echo "YES" || echo "NO";
ericson.cepeda
3
Shellcheck beschwert sich über diese Lösung, SC2199 und SC2076. Ich konnte die Warnungen nicht beheben, ohne die Funktionalität zu beeinträchtigen. Haben Sie andere Gedanken dazu als das Deaktivieren des Shellchecks für diese Zeile?
Ali Essam
4
SC2076 ist einfach zu reparieren, entfernen Sie einfach die doppelten Anführungszeichen in der if. Ich glaube nicht, dass es einen Weg gibt, SC2199 mit diesem Ansatz zu vermeiden . Sie müssten das Array explizit durchlaufen, wie in einigen anderen Lösungen gezeigt, oder die Warnung ignorieren .
Keegan
388

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
Patrik
quelle
5
Funktioniert gut! Ich muss nur daran denken, das Array wie in Anführungszeichen zu übergeben : "${array[@]}". Andernfalls beeinträchtigen Elemente, die Leerzeichen enthalten, die Funktionalität.
Juve
26
Nett. Ich würde es elementIn () nennen, weil es prüft, ob das erste Argument im zweiten enthalten ist. enthältElements () klingt so, als würde das Array zuerst gehen. Für Neulinge wie mich wäre ein Beispiel für die Verwendung einer Funktion, die nicht in einer "if" -Anweisung in stdout schreibt, hilfreich: if elementIn "$table" "${skip_tables[@]}" ; then echo skipping table: ${table}; fi; Vielen Dank für Ihre Hilfe!
GlenPeterson
5
@Bluz das && Konstrukt ist ein boolescher AND-Operator. Die Verwendung von booleschen Operatoren erstellt eine boolesche Anweisung. Die boolesche Logik besagt, dass die gesamte Anweisung nur dann wahr sein kann, wenn beide Anweisungen vor und nach dem && als wahr ausgewertet werden. Dies wird als Verknüpfung für und wenn Block verwendet. Der Test wird ausgewertet. Wenn er falsch ist, muss die Rückgabe nicht ausgewertet werden, da sie für die gesamte Anweisung irrelevant ist, sobald der Test fehlgeschlagen ist und daher nicht ausgeführt wird. Wenn der Test erfolgreich ist, muss für den Erfolg der booleschen Anweisung das Ergebnis der Rückgabe ermittelt werden, damit der Code ausgeführt wird.
Peteches
4
@James Laut Konvention ist der Erfolgscode in bash "0" und der Fehler ist alles> = 1. Deshalb gibt er bei Erfolg 0 zurück. :)
tftd
11
@Stelios shiftverschiebt die Argumentliste um 1 nach links ( Löschen des ersten Arguments) und forohne inimplizite Iteration über die Argumentliste.
Christian
58
$ myarray=(one two three)
$ case "${myarray[@]}" in  *"two"*) echo "found" ;; esac
found
Ghostdog74
quelle
69
Beachten Sie, dass dies nicht jedes Element im Array einzeln durchläuft. Stattdessen wird das Array einfach verkettet und "zwei" als Teilzeichenfolge abgeglichen. Dies kann zu unerwünschtem Verhalten führen, wenn getestet wird, ob das genaue Wort "zwei" ein Element im Array ist.
MartyMacGyver
Ich dachte, das würde für mich beim Vergleichen von Dateitypen funktionieren, stellte jedoch fest, dass mit zunehmenden Zählern zu viele Werte gezählt wurden ... boo!
Mike Q
17
falsch! Grund: case "${myarray[@]}" in *"t"*) echo "found" ;; esacAusgaben:found
Sergej Jevsejev
@MartyMacGyver, könnten Sie bitte auf meine Ergänzung zu dieser Antwort Stackoverflow.com/a/52414872/1619950
Aleksandr Podkutin
45
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
Scott
quelle
Sie können jedoch eine indizierte for-Schleife verwenden und vermeiden, getötet zu werden, wenn ein Array-Element IFS enthält: for ((i = 0; i <$ {# array [@]}; i ++))
mkb
@Matt: Sie müssen vorsichtig sein, ${#}da Bash spärliche Arrays unterstützt.
Bis auf weiteres angehalten.
@Paolo, wenn Ihr Array ein Leerzeichen enthält, vergleichen Sie es einfach als Zeichenfolge. Ein Leerzeichen ist auch eine Zeichenfolge.
Scott
@Paolo: Sie können dies zu einer Funktion machen, aber Arrays können nicht als Argumente übergeben werden, sodass Sie sie als global behandeln müssen.
Bis auf weiteres angehalten.
Dennis hat recht. Aus dem Bash-Referenzhandbuch: "Wenn das Wort in doppelten Anführungszeichen steht, ... $ {name [@]} erweitert jedes Namenselement zu einem separaten Wort"
mkb
37

Einzeilige Lösung

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

Erläuterung

Die printfAnweisung druckt jedes Element des Arrays in einer separaten Zeile.

Die grepAnweisung verwendet die Sonderzeichen ^und $sucht nach einer Zeile, die genau das angegebene Muster enthält mypattern(nicht mehr und nicht weniger).


Verwendungszweck

Um dies in eine if ... thenErklärung zu setzen:

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

Ich -qhabe dem grepAusdruck ein Flag hinzugefügt , damit keine Übereinstimmungen gedruckt werden. es wird nur die Existenz eines Matches als "wahr" behandeln.

JellicleCat
quelle
Schöne Lösung! Auf GNU grep gibt es auch "--line-regexp", das "-P" und die ^ und $ im Muster ersetzen könnte: printf '% s \ n' $ {myarray [@]} | grep -q --line-regexp 'mypattern'
presto8
19

Wenn Sie Leistung benötigen, möchten Sie nicht bei jeder Suche das gesamte Array durchlaufen.

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=("$@")
  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 können Sie es so verwenden:

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

Und testen Sie die Mitgliedschaft so:

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 Wertes innerhalb des Arrays mit:

echo "<< ${myarray_index[$member]} >> is the index of $member"
LeoRochael
quelle
+1 für die Idee, dass Sie ein assoziatives Array verwenden sollten. Ich denke, der Code für make_indexist aufgrund der Indirektion etwas ausgefeilter. Sie hätten einen festen Array-Namen mit einem viel einfacheren Code verwenden können.
Musiphil
17

Ich benutze normalerweise nur:

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

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

Sean DiSanti
quelle
Das ist definitiv die einfachste Lösung - sollte meiner Meinung nach als Antwort markiert werden. Zumindest meine Gegenstimme! [:
ToVine
2
Bei ähnlichen Nadeln funktioniert das nicht. Zum Beispielhaystack=(needle1 needle2); echo ${haystack[@]} | grep -o "needle" | wc -w
Keegan
1
Sehr richtig. Das Verbinden mit einem Begrenzer, der in keinem Element vorhanden ist, und das Hinzufügen zur Nadel würde dabei helfen. Vielleicht so etwas wie ... (ungetestet)inarray=$(printf ",%s" "${haystack[@]}") | grep -o ",needle" | wc -w)
Sean DiSanti
2
Die Verwendung von grep -x würde Fehlalarme vermeiden: inarray=$(printf ",%s" "${haystack[@]}") | grep -x "needle" | wc -l
jesjimher
Vielleicht einfach inarray=$(echo " ${haystack[@]}" | grep -o " needle" | wc -w)als -x bewirkt, dass grep versucht, die gesamte Eingabezeichenfolge abzugleichen
MI Wright
17

Ein weiterer Liner ohne Funktion:

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

Vielen Dank an @Qwerty für die Hinweise zu Leerzeichen!

entsprechende Funktion:

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

Beispiel:

some_words=( these are some words )
find_in_array word "${some_words[@]}" || echo "expected missing! since words != word"
estani
quelle
1
Warum brauchen wir hier eine Unterschale?
Codeforester
1
@codeforester das ist alt ... aber wie es geschrieben wurde, brauchst du es, um davon abzubrechen, das ist was das exit 0macht (hört so schnell wie möglich auf, wenn es gefunden wird).
Estani
Das Ende des einen Liners sollte || echo not foundanstelle von sein, || not foundoder die Shell versucht, einen Befehl mit dem Namen not mit gefundenem Argument auszuführen , wenn sich der angeforderte Wert nicht im Array befindet.
Zoke
11
containsElement () { for e in "${@:2}"; do [[ "$e" = "$1" ]] && return 0; done; return 1; }

Behandelt jetzt leere Arrays korrekt.

Yann
quelle
Wie unterscheidet sich das von der Antwort von @ patrik? Der einzige Unterschied, den ich sehe, ist "$e" = "$1"(statt "$e" == "$1"), der wie ein Fehler aussieht.
CivFan
1
Es ist nicht. @ patrik hat meinen Kommentar in seiner ursprünglichen Antwort damals zusammengeführt (Patch # 4). Hinweis: "e" == "$1"ist syntaktisch klarer.
Yann
@CivFan In seiner aktuellen Form ist dies aufgrund des eleganten $ {@: 2} und des selbstdokumentierenden $ 1 kürzer als die in Patriks Antwort. Ich würde hinzufügen, dass innerhalb von [[]] kein Zitieren erforderlich ist.
Hontvári Levente
9

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.

hornetbzz
quelle
Dieser hat mir sehr geholfen. Vielen Dank!
Ed Manet
Die Frage sagte nicht explizit, dass Sie die richtige Antwort geben mussten, aber ich denke, das ist in der Frage impliziert ... Das Array enthält nicht den Wert "zwei".
Tetsujin
Das obige wird eine Übereinstimmung für 'rd' melden.
Noel Yap
6

Wenn Sie einen schnellen und schmutzigen Test durchführen möchten, um festzustellen, ob es sich lohnt, das gesamte Array zu durchlaufen, um eine genaue Übereinstimmung zu erzielen, kann Bash Arrays wie Skalare behandeln. Testen Sie auf eine Übereinstimmung im Skalar. Wenn keine vorhanden ist, spart das Überspringen der Schleife Zeit. Offensichtlich können Sie falsch positive Ergebnisse 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. Damit array=(word "two words" something)wird nur "Checking" ausgegeben. Mit array=(word "two widgets" something)wird es keine Ausgabe geben.

Bis auf weiteres angehalten.
quelle
Warum nicht einfach durch wordseinen regulären Ausdruck ersetzen ^words$, der nur der gesamten Zeichenfolge entspricht, wodurch die Notwendigkeit, jedes Element einzeln zu überprüfen, vollständig entfällt?
Dejay Clayton
@DejayClayton: Weil pattern='^words$'; if [[ ${array[@]} =~ $pattern ]]niemals übereinstimmen wird, da das gesamte Array auf einmal überprüft wird, als wäre es ein Skalar. Die einzelnen Überprüfungen in meiner Antwort dürfen nur durchgeführt werden, wenn es einen Grund gibt, aufgrund der groben Übereinstimmung fortzufahren.
Bis auf weiteres angehalten.
Ah, ich verstehe, was du versuchst zu tun. Ich habe eine Antwortvariante vorgeschlagen, die leistungsfähiger und sicherer ist.
Dejay Clayton
6

Das funktioniert bei mir:

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

Beispielaufruf:

arr=("abc" "xyz" "123")
if contains arr "abcx"; then
    echo "Yes"
else
    echo "No"
fi
Chris Prince
quelle
5
a=(b c d)

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

Wenn Sie es vorziehen, können Sie gleich lange Optionen verwenden:

--fixed-strings --quiet --line-regexp --null-data
Steven Penny
quelle
1
Dies funktioniert nicht mit BSD-grep auf dem Mac, da keine --null-Daten vorhanden sind. :(
Will
4

Angelehnt an Dennis Williamson ‚s Antwort , die folgende Lösung kombiniert Arrays, Shell sicher zitiert, und regulären Ausdrücken die Notwendigkeit zu vermeiden: Iteration über Schleifen; Verwendung von 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 verwendet reguläre Bash-Ausdrücke, um eine Übereinstimmung mit einer stringifizierten Version des Array-Inhalts herzustellen. Es gibt sechs wichtige Schritte, um sicherzustellen, dass die Übereinstimmung mit regulären Ausdrücken nicht durch clevere Wertekombinationen innerhalb des Arrays getäuscht werden kann:

  1. Erstellen Sie die Vergleichszeichenfolge mithilfe der integrierten Funktion von Bash printf Shell-Zitat %q. Durch Shell-Anführungszeichen wird sichergestellt, dass Sonderzeichen "Shell-sicher" werden, indem sie mit einem Backslash maskiert werden \.
  2. Wählen Sie ein Sonderzeichen als Werttrennzeichen. Das Trennzeichen MUSS eines der Sonderzeichen sein, die bei der Verwendung maskiert werden%q maskiert werden. Dies ist die einzige Möglichkeit, um sicherzustellen, dass Werte innerhalb des Arrays nicht auf clevere Weise konstruiert werden können, um die Übereinstimmung mit regulären Ausdrücken zu täuschen. Ich wähle Komma, weil dieses Zeichen am sichersten ist, wenn es auf eine ansonsten unerwartete Weise bewertet oder missbraucht wird.
  3. Kombinieren Sie alle Array-Elemente zu einer einzigen Zeichenfolge, wobei Sie zwei Instanzen des Sonderzeichens als Trennzeichen verwenden. Am Beispiel von Komma habe ich verwendet,,%q als Argument verwendet printf. Dies ist wichtig, da zwei Instanzen des Sonderzeichens nur dann nebeneinander angezeigt werden können, wenn sie als Trennzeichen angezeigt werden. Alle anderen Instanzen des Sonderzeichens werden maskiert.
  4. Hängen Sie zwei nachfolgende Instanzen des Trennzeichens an die Zeichenfolge an, um Übereinstimmungen mit dem letzten Element des Arrays zu ermöglichen. Statt also gegen zu vergleichen ${array_str}, zu vergleichen , gegen ${array_str},,.
  5. Wenn die gesuchte Zielzeichenfolge von einer Benutzervariablen bereitgestellt wird, müssen Sie alle Instanzen des Sonderzeichens mit einem Backslash maskieren. Andernfalls wird die Übereinstimmung mit regulären Ausdrücken anfällig dafür, von clever gestalteten Array-Elementen getäuscht zu werden.
  6. Führen Sie eine Bash-Übereinstimmung mit regulären Ausdrücken für die Zeichenfolge durch.
Dejay Clayton
quelle
Sehr schlau. Ich kann sehen, dass die meisten potenziellen Probleme verhindert werden, aber ich möchte testen, ob es Eckfälle gibt. Außerdem würde ich gerne ein Beispiel für die Behandlung von Punkt 5 sehen. So etwas wie printf -v pattern ',,%q,,' "$user_input"; if [[ "${array_str},," =~ $pattern ]]vielleicht.
Bis auf weiteres angehalten.
case "$(printf ,,%q "${haystack[@]}"),," in (*"$(printf ,,%q,, "$needle")"*) true;; (*) false;; esac
Tino
3

Eine kleine Ergänzung zu der Antwort von @ ghostdog74 über die Verwendung von caseLogik zum Überprüfen, ob das Array einen bestimmten Wert enthält:

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

Oder extglobwenn die Option aktiviert ist, können Sie dies folgendermaßen tun:

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

Wir können es auch mit ifAussage machen:

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

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

wo

c is element separator
p is regex pattern

(Der Grund für die separate Zuweisung von p anstelle der Verwendung des Ausdrucks direkt in [[]] besteht darin, die Kompatibilität für Bash 4 aufrechtzuerhalten.)

Beorn Harris
quelle
liebe deine Verwendung des Wortes "einfach" hier ... 😂
Christian
2

Wenn Sie einige der hier vorgestellten Ideen kombinieren, können Sie eine elegante Aussage ohne Schleifen erstellen, die exakte Wortübereinstimmungen liefert .

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

Dies wird nicht ausgelöst wordoder valnur ganze Wortübereinstimmungen. Es wird unterbrochen, wenn jeder Array-Wert mehrere Wörter enthält.

Ecker00
quelle
1

Ich schreibe diese Art von Dienstprogrammen im Allgemeinen, um den Namen der Variablen und nicht den Variablenwert zu verarbeiten, 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.

Barry Kelly
quelle
Kann jemand ein Beispiel dafür posten, das in einem if verwendet wird, insbesondere wie Sie das Array übergeben. Ich versuche zu überprüfen, ob ein Argument an das Skript übergeben wurde, indem ich die Parameter als Array behandle, aber es möchte nicht funktionieren. params = ("$ @") check = array_contains $ {params} 'SKIPDIRCHECK' if [[$ {check} == 1]]; dann ... Aber wenn das Skript mit 'asas' als Argument ausgeführt wird, heißt es immer wieder asas: Befehl nicht gefunden. : /
Steve Childs
1

Verwenden von grepundprintf

Formatieren Sie jedes Array-Element in einer neuen Zeile und dann in grepden Zeilen.

if printf '%s\n' "${array[@]}" | grep -x -q "search string"; then echo true; else echo false; fi
Beispiel:
$ 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 Trennzeichen und Leerzeichen hat.

Qwerty
quelle
1

Einzeilige Prüfung ohne 'grep' und Schleifen

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

Dieser Ansatz verwendet weder externe Dienstprogramme wie grepnoch Schleifen.

Was hier passiert, ist:

  • Wir verwenden einen Platzhalter-Teilzeichenfolgen-Matcher, um unser Element in dem Array zu finden, das zu einer Zeichenfolge verkettet ist.
  • Wir schneiden mögliche Fehlalarme ab, indem wir unseren Suchbegriff zwischen zwei Trennzeichen einfügen.
  • Wir verwenden ein nicht druckbares Zeichen als Trennzeichen, um auf der sicheren Seite zu sein.
  • Wir erreichen, dass unser Trennzeichen auch für die Array-Verkettung verwendet wird, indem wir das vorübergehend ersetzen IFS Variablenwert wird.
  • Wir machen diese IFSWertersetzung vorübergehend, indem wir unseren bedingten Ausdruck in einer Unterschale (in zwei Klammern) auswerten.
Sergey Ushakov
quelle
Beseitigen Sie dlm. Verwenden Sie IFS direkt.
Robin A. Meade
Dies ist die beste Antwort. Es hat mir so gut gefallen, dass ich eine Funktion mit dieser Technik geschrieben habe .
Robin A. Meade
1

Parametererweiterung verwenden:

$ {parameter: + word} Wenn der Parameter null oder nicht gesetzt ist, wird nichts ersetzt, andernfalls wird die Erweiterung des Wortes ersetzt.

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
Gabriel Laden
quelle
${myarray[hello]:+_}Funktioniert hervorragend für assoziative Arrays, jedoch nicht für übliche indizierte Arrays. Bei der Frage geht es darum, einen Wert in einem Array zu finden und nicht zu überprüfen, ob der Schlüssel eines assoziativen Arrays vorhanden ist.
Eric
0

Nachdem ich geantwortet hatte, las ich eine andere Antwort, die mir besonders gut gefallen hat, aber sie war fehlerhaft und wurde abgelehnt. Ich habe mich inspirieren lassen und hier sind zwei neue Ansätze, die ich für realisierbar halte.

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

mit grepund 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 hinzufügen || <run_your_if_notfound_command_here>

Qwerty
quelle
0

Hier ist meine Meinung dazu.

Ich würde lieber keine Bash-for-Schleife verwenden, wenn ich dies vermeiden kann, da die Ausführung einige Zeit in Anspruch nimmt. Wenn sich etwas wiederholen muss, lassen Sie es in einer niedrigeren Sprache als einem Shell-Skript geschrieben sein.

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

Dies funktioniert durch Erstellen eines temporären assoziativen Arrays, _arrdessen 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 Versionen von Bash nicht funktioniert.) Wir haben festgelegt $IFS, dass die Wortaufteilung in Leerzeichen vermieden wird.

Die Funktion enthält keine expliziten Schleifen, obwohl intern Schritte durch das Eingabearray geschlagen werden, um sie zu füllen printf. Das printf-Format stellt %qsicher, dass Eingabedaten maskiert 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 auch bei der Befehlserweiterung nicht von externen Pipes nach unten gezogen werden.

Und wenn Sie nicht gerne eval... nun, Sie können einen anderen Ansatz verwenden. :-)

Ghoti
quelle
Was ist, wenn das Array eckige Klammern enthält?
gniourf_gniourf
@gniourf_gniourf - scheint in Ordnung zu sein, wenn eckige Klammern ausgeglichen sind, aber ich kann sehen, dass es ein Problem ist, wenn Ihr Array Werte mit unausgeglichenen eckigen Klammern enthält. In diesem Fall würde ich die evalAnweisung am Ende der Antwort aufrufen . :)
Ghoti
Es ist nicht so, dass ich es nicht mag eval(ich habe nichts dagegen, im Gegensatz zu den meisten Menschen, die weinen, evalist böse, meistens ohne zu verstehen, was daran böse ist). Nur dass dein Befehl gebrochen ist. Vielleicht %qstatt %swäre besser.
gniourf_gniourf
1
@gniourf_gniourf: Ich meinte nur den "anderen Ansatz" (und ich bin evalnatürlich total bei dir ), aber du hast absolut Recht, %qscheint zu helfen, ohne irgendetwas anderes zu beschädigen, was ich sehen kann. (Ich wusste nicht, dass% q auch eckigen Klammern entgehen würde.) Ein weiteres Problem, das ich gesehen und behoben habe, betraf Leerzeichen. Mit a=(one "two " three), ähnlich wie bei Keegans Problem: Nicht nur array_contains a "two "ein falsches Negativ, sondern array_contains a twoauch ein falsches Positiv. Einfach genug, um durch Einstellen zu reparieren IFS.
Ghoti
Liegt es nicht an Leerzeichen, weil Anführungszeichen fehlen? es bricht auch mit Glob-Zeichen. Ich denke, du willst das stattdessen: eval _arr=( $(eval printf '[%q]="1"\ ' "\"\${$1[@]}\"") )und du kannst das fallen lassen local IFS=. Es gibt immer noch ein Problem mit leeren Feldern im Array, da Bash sich weigert, einen leeren Schlüssel in einem assoziativen Array zu erstellen. Eine schnelle Möglichkeit, dies zu beheben, besteht darin, einem Dummy-Charakter ein Präfix voranzustellen, z. B x.: eval _arr=( $(eval printf '[x%q]="1"\ ' "\"\${$1[@]}\"") )Und return $(( 1 - 0${_arr[x$2]} )).
gniourf_gniourf
-1

Meine bereits vorgeschlagene Version der Technik für reguläre Ausdrücke:

values=(foo bar)
requestedValue=bar

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

Was hier passiert, ist, dass Sie das gesamte Array der unterstützten Werte in Wörter erweitern und jedem von ihnen eine bestimmte Zeichenfolge, in diesem Fall "X-", voranstellen und dasselbe mit dem angeforderten Wert tun. Wenn dieser tatsächlich im Array enthalten ist, stimmt die resultierende Zeichenfolge höchstens mit einem der resultierenden Token überein oder im Gegenteil überhaupt nicht. Im letzteren Fall ist das || Der Operator wird ausgelöst und Sie wissen, dass es sich um einen nicht unterstützten Wert handelt. Zuvor wird der angeforderte Wert durch standardmäßige Manipulation von Shell-Strings von allen führenden und nachfolgenden Leerzeichen befreit.

Ich glaube, es ist sauber und elegant, obwohl ich mir nicht sicher bin, wie leistungsfähig es sein kann, wenn Ihr Array unterstützter Werte besonders groß ist.

jmpp
quelle
-1

Hier ist meine Sicht auf dieses Problem. Hier ist die Kurzversion:

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

Und die lange Version, die meiner Meinung nach viel augenschonender 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
Robert
quelle
Ich habe bash schon so lange nicht mehr benutzt, dass es mir schwer fällt, die Antworten oder sogar das, was ich selbst geschrieben habe, zu verstehen :) Ich kann nicht glauben, dass diese Frage nach all der Zeit immer noch aktiv wird :)
Paolo Tedesco
Was ist mit test_arr=("hello" "world" "two words")?
Qwerty
-1

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 hat folgendes funktioniert:

# 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önnen es auch folgendermaßen kürzen / verdichten:

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

In meinem Fall habe ich jq ausgeführt, um JSON 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. Es funktioniert nicht für manuell erstellte Arrays des Typs, LIST=("1" "2" "4")sondern für durch Zeilenumbrüche getrennte Skriptausgabe.


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

E. Körner
quelle
-2

Der folgende Code prüft, ob sich ein bestimmter Wert im Array befindet, und gibt seinen auf Null basierenden Offset zurück:

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

Die Übereinstimmung wird mit den vollständigen Werten durchgeführt, daher würde das Setzen von VALUE = "drei" nicht übereinstimmen.

Sven Rieke
quelle
-2

Dies könnte eine Untersuchung wert sein, wenn Sie nicht iterieren möchten:

#!/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 angepasst von: http://www.thegeekstuff.com/2010/06/bash-array-tutorial/ Ich finde es ziemlich clever.

EDIT: Sie könnten wahrscheinlich nur tun:

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

Letzteres funktioniert jedoch nur, wenn das Array eindeutige Werte enthält. Wenn Sie in "143" nach 1 suchen, erhalten Sie falsch positive Ergebnisse.

Sigg3.net
quelle
-2

Ein bisschen spät, aber Sie könnten dies verwenden:

#!/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
Coder-256
quelle
-2

Ich habe mir dieses ausgedacht, das nur in zsh funktioniert, aber ich finde den allgemeinen Ansatz nett.

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

Sie nehmen Ihr Muster nur dann aus jedem Element heraus, wenn es damit beginnt ${arr[@]/#pattern/}oder endet ${arr[@]/%pattern/}. Diese beiden Substitutionen funktionieren in Bash, aber beide gleichzeitig${arr[@]/#%pattern/} funktionieren nur in zsh.

Wenn das geänderte Array dem Original entspricht, enthält es das Element nicht.

Bearbeiten:

Dieser arbeitet in Bash:

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

Nach der Substitution wird die Länge beider Arrays verglichen. Wenn das Array das Element enthält, wird es durch die Ersetzung vollständig gelöscht, und die Anzahl unterscheidet sich.

spelufo
quelle