Sprawdź, czy tablica Bash zawiera wartość

443

W Bash, jaki jest najprostszy sposób sprawdzenia, czy tablica zawiera określoną wartość?

Edycja : Z pomocą odpowiedzi i komentarzy po kilku testach wymyśliłem:

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

Nie jestem pewien, czy to najlepsze rozwiązanie, ale wydaje się, że działa.

Paolo Tedesco
źródło

Odpowiedzi:

457

Zaletą tego podejścia jest to, że nie trzeba zapętlać wszystkich elementów (przynajmniej nie jawnie). Ale ponieważ array_to_string_internal()w array.c nadal zapętla elementy tablicy i łączy je w ciąg, prawdopodobnie nie jest to bardziej wydajne niż proponowane rozwiązania zapętlania, ale jest bardziej czytelne.

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

Zauważ, że w przypadkach, gdy szukana wartość jest jednym ze słów w elemencie tablicy ze spacjami, da fałszywe alarmy. Na przykład

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

Wyrażenie regularne zobaczy, że „Jack” znajduje się w tablicy, nawet jeśli nie jest. Musisz więc zmienić IFSi znaki separatora w wyrażeniu regularnym, jeśli nadal chcesz używać tego rozwiązania, takiego jak ten

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

Spowoduje to wydrukowanie „false”.

Oczywiście można to również wykorzystać jako oświadczenie testowe, pozwalając na wyrażenie go jako jednowierszowego

[[ " ${array[@]} " =~ " ${value} " ]] && echo "true" || echo "false"
Keegan
źródło
1
Dodałem spację na początku pierwszego dopasowania wartości wyrażenia regularnego, aby pasowało ono tylko do słowa, a nie do końcówki tego słowa. Działa świetnie. Nie rozumiem jednak, dlaczego używasz drugiego warunku, czy pierwszy nie działałby dobrze sam?
JStrahl
1
@AwQiruiGuo Nie jestem pewien, czy śledzę. Czy mówisz o tablicach z literałami dolara? Jeśli tak, upewnij się, że uciekasz od dolarów w wartości, do której się dopasowujesz, z odwrotnym ukośnikiem.
Keegan
10
Oneliner: [[ " ${branches[@]} " =~ " ${value} " ]] && echo "YES" || echo "NO";
ericson.cepeda
3
Shellcheck narzeka na to rozwiązanie, SC2199 i SC2076. Nie mogłem naprawić ostrzeżeń bez zepsucia funkcjonalności. Masz jakieś przemyślenia na ten temat oprócz wyłączenia sprawdzania powłoki dla tej linii?
Ali Essam
4
SC2076 jest łatwy do naprawienia, wystarczy usunąć podwójne cudzysłowy w if. Nie sądzę, aby można było uniknąć SC2199 przy takim podejściu. Musisz jawnie zapętlić tablicę, jak pokazano w niektórych innych rozwiązaniach, lub zignorować ostrzeżenie.
Keegan
388

Poniżej znajduje się mała funkcja do osiągnięcia tego. Wyszukiwany ciąg jest pierwszym argumentem, a reszta to elementy tablicy:

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

Uruchomienie testowe tej funkcji może wyglądać następująco:

$ array=("something to search for" "a string" "test2000")
$ containsElement "a string" "${array[@]}"
$ echo $?
0
$ containsElement "blaha" "${array[@]}"
$ echo $?
1
patrik
źródło
5
Działa ładnie! Muszę tylko pamiętać, aby przekazać tablicę jako cytatami: "${array[@]}". W przeciwnym razie elementy zawierające spacje spowodują uszkodzenie funkcjonalności.
Juve,
26
Miły. Nazwałbym to elementIn (), ponieważ sprawdza, czy pierwszy argument znajduje się w drugim. zawiera zawieraElements () brzmi, jakby tablica poszła pierwsza. Dla początkujących, takich jak ja, przykład użycia funkcji, która nie zapisuje stdout w instrukcji „if”, pomógłby: if elementIn "$table" "${skip_tables[@]}" ; then echo skipping table: ${table}; fi; Dzięki za pomoc!
GlenPeterson
5
@Bluz konstrukcja && jest operatorem logicznym AND. Zastosowanie operatorów boolowskich tworzy instrukcję boolowską. Logika boolowska mówi, że cała instrukcja może być prawdziwa tylko wtedy, gdy obie instrukcje przed i po && zostaną ocenione jako prawdziwe. Jest to używane jako skrót dla i jeśli blok. Test jest oceniany, a jeśli jest fałszywy, nie ma potrzeby oceny zwrotu, ponieważ nie ma on znaczenia dla całej instrukcji po niepowodzeniu testu i dlatego nie jest uruchamiany. Jeśli test zakończy się powodzeniem, sukces instrukcji boolean NIE wymaga ustalenia wyniku powrotu, aby kod został uruchomiony.
peteches,
4
@James, zgodnie z konwencją, kod sukcesu w bashu to „0”, a błąd to wszystko> = 1. Dlatego zwraca 0 po sukcesie. :)
tftd
11
@Stelios shiftprzesuwa listę argumentów o 1 w lewo (upuszczając pierwszy argument) i forbez inpośrednio iteruje listę argumentów.
Christian
58
$ myarray=(one two three)
$ case "${myarray[@]}" in  *"two"*) echo "found" ;; esac
found
ghostdog74
źródło
69
Zauważ, że to nie iteruje każdego elementu w tablicy osobno ... zamiast tego po prostu konkatenuje tablicę i dopasowuje „dwa” jako podłańcuch. Może to powodować niepożądane zachowanie, jeśli ktoś sprawdza, czy dokładne słowo „dwa” jest elementem tablicy.
MartyMacGyver,
Myślałem, że to zadziała dla mnie przy porównywaniu typów plików, ale odkryłem, że wraz ze wzrostem liczników zlicza zbyt wiele wartości ... boo!
Mike Q
17
źle! Powód: case "${myarray[@]}" in *"t"*) echo "found" ;; esacwyniki:found
Sergej Jevsejev
@MartyMacGyver, czy mógłbyś spojrzeć na mój dodatek do tej odpowiedzi stackoverflow.com/a/52414872/1619950
Aleksandr Podkutin
45
for i in "${array[@]}"
do
    if [ "$i" -eq "$yourValue" ] ; then
        echo "Found"
    fi
done

Dla ciągów:

for i in "${array[@]}"
do
    if [ "$i" == "$yourValue" ] ; then
        echo "Found"
    fi
done
Scott
źródło
To powiedziawszy, możesz użyć indeksowanej pętli for i uniknąć zabicia, gdy element tablicy zawiera IFS: for ((i = 0; i <$ {# array [@]}; i ++))
mkb 10.10
@Matt: Musisz być ostrożny, ${#}ponieważ Bash obsługuje rzadkie tablice.
Wstrzymano do odwołania.
@Paolo, jeśli tablica zawiera spację, po prostu porównaj ją jako ciąg znaków. spacja jest również ciągiem znaków.
Scott,
@Paolo: Możesz uczynić tę funkcję, ale tablic nie można przekazywać jako argumentów, więc będziesz musiał traktować ją jako globalną.
Wstrzymano do odwołania.
Dennis ma rację. Z podręcznika referencyjnego bash: „Jeśli słowo jest cytowane podwójnie, ... $ {nazwa [@]} rozwija każdy element nazwy do osobnego słowa”
mkb
37

Rozwiązanie jednowierszowe

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

Wyjaśnienie

printfOświadczenie drukuje każdy element tablicy w osobnej linii.

grepOświadczenie używa znaków specjalnych ^i $znaleźć wiersz zawierający dokładnie wzór podany jako mypattern(nie więcej, nie mniej).


Stosowanie

Aby umieścić to w if ... thenoświadczeniu:

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

Dodałem -qflagę do grepwyrażenia, aby nie drukowało dopasowań; po prostu potraktuje istnienie dopasowania jako „prawdziwe”.

JellicleCat
źródło
Fajne rozwiązanie! W GNU grep istnieje również „--line-regexp”, które może zastąpić „-P” oraz ^ i $ we wzorcu: printf '% s \ n' $ {myarray [@]} | grep -q --line-regexp 'mypattern'
presto8
19

Jeśli potrzebujesz wydajności, nie chcesz za każdym razem przeszukiwać całej tablicy.

W takim przypadku możesz utworzyć tablicę asocjacyjną (tablicę skrótów lub słownik), która reprezentuje indeks tej tablicy. Oznacza to, że mapuje każdy element tablicy na swój indeks w tablicy:

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
}

Następnie możesz użyć tego w następujący sposób:

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

I przetestuj członkostwo tak:

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

Lub też:

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

Zauważ, że to rozwiązanie działa poprawnie, nawet jeśli w testowanej wartości lub w wartościach tablic są spacje.

Jako bonus otrzymujesz również indeks wartości w tablicy z:

echo "<< ${myarray_index[$member]} >> is the index of $member"
LeoRochael
źródło
+1 za pomysł, że powinieneś używać tablicy asocjacyjnej. Myślę, że kod make_indexjest nieco bardziej wymyślny ze względu na pośrednie; mogłeś użyć stałej nazwy tablicy ze znacznie prostszym kodem.
musiphil
17

Zwykle używam:

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

wartość niezerowa oznacza, że ​​znaleziono dopasowanie.

Sean DiSanti
źródło
To prawda, że ​​jest to zdecydowanie najłatwiejsze rozwiązanie - moim zdaniem należy zaznaczyć odpowiedź. Przynajmniej miej moje poparcie! [:
ToVine,
2
To nie zadziała w przypadku podobnych igieł. Na przykładhaystack=(needle1 needle2); echo ${haystack[@]} | grep -o "needle" | wc -w
Keegan
1
Bardzo prawdziwe. pomogłoby to połączyć z ogranicznikiem nieobecnym w żadnym elemencie i dodać go do igły. Może coś takiego ... (niesprawdzone)inarray=$(printf ",%s" "${haystack[@]}") | grep -o ",needle" | wc -w)
Sean DiSanti
2
Użycie grep -x pozwoliłoby uniknąć fałszywych alarmów: inarray=$(printf ",%s" "${haystack[@]}") | grep -x "needle" | wc -l
jesjimher 7.04.16
Być może po prostu inarray=$(echo " ${haystack[@]}" | grep -o " needle" | wc -w)jako -x powoduje, że grep próbuje dopasować cały ciąg wejściowy
MI Wright
17

Kolejna wkładka bez funkcji:

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

Dzięki @Qwerty za informacje dotyczące spacji!

odpowiednia funkcja:

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

przykład:

some_words=( these are some words )
find_in_array word "${some_words[@]}" || echo "expected missing! since words != word"
estani
źródło
1
Dlaczego potrzebujemy tutaj podpowłoki?
codeforester
1
@codeforester to jest stare ... ale jak zostało napisane, potrzebujesz go, aby się od niego oderwać, właśnie to exit 0robi (zatrzymuje się jak najszybciej, jeśli zostanie znaleziony).
estani
Koniec jednej linijki powinien być || echo not foundzamiast || not foundlub powłoka spróbuje wykonać polecenie o nazwie nie z znalezionym argumentem, jeśli żądanej wartości nie ma w tablicy.
zoke
11
containsElement () { for e in "${@:2}"; do [[ "$e" = "$1" ]] && return 0; done; return 1; }

Teraz poprawnie obsługuje puste tablice.

Yann
źródło
Czym różni się to od odpowiedzi @ patrik? Jedyną różnicą, którą widzę, jest "$e" = "$1"(zamiast "$e" == "$1") która wygląda jak błąd.
CivFan,
1
Nie jest. @ patrik połączył mój komentarz w swojej oryginalnej odpowiedzi (łatka # 4). Uwaga: "e" == "$1"jest jaśniejszy składniowo.
Yann,
@CivFan W obecnej formie jest on krótszy niż ten w odpowiedzi Patryka, ze względu na elegancki $ {@: 2} i samo dokumentowanie 1 $. Dodałbym, że cytowanie nie jest konieczne w [[]].
Hontvári Levente
9

Oto niewielki wkład:

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

Uwaga: w ten sposób nie rozróżnia się przypadku „dwa słowa”, ale nie jest to wymagane w pytaniu.

hornetbzz
źródło
Ten bardzo mi pomógł. Dzięki!
Ed Manet
Pytanie nie mówi wprost, że musisz udzielić poprawnej odpowiedzi, ale myślę, że jest to ukryte w pytaniu ... Tablica nie zawiera wartości „dwa”.
tetsujin
Powyżej zgłosi dopasowanie dla „rd”.
Noel Yap,
6

Jeśli chcesz zrobić szybki i brudny test, aby sprawdzić, czy warto iterować całą tablicę, aby uzyskać dokładne dopasowanie, Bash może traktować tablice jak skalary. Sprawdź dopasowanie w skalarach, jeśli nie, pomijanie pętli pozwala zaoszczędzić czas. Oczywiście możesz uzyskać fałszywe alarmy.

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

Spowoduje to wyświetlenie „Sprawdzanie” i „Dopasuj”. Dzięki array=(word "two words" something)temu wyświetli się tylko „Sprawdzanie”. Dzięki array=(word "two widgets" something)nie będzie wyjścia.

Wstrzymano do odwołania.
źródło
Dlaczego nie zastąpić wordswyrażeniem regularnym, ^words$które pasuje tylko do całego łańcucha, co całkowicie eliminuje potrzebę sprawdzania każdego elementu osobno?
Dejay Clayton
@DejayClayton: Ponieważ pattern='^words$'; if [[ ${array[@]} =~ $pattern ]]nigdy nie będzie pasować, ponieważ sprawdza całą tablicę naraz, jakby to był skalar. Indywidualne kontrole w mojej odpowiedzi należy wykonać tylko wtedy, gdy istnieje powód, aby kontynuować na podstawie przybliżonego dopasowania.
Wstrzymano do odwołania.
Ach, widzę, co próbujesz zrobić. Zaproponowałem wariant odpowiedzi, który jest bardziej wydajny i bezpieczny.
Dejay Clayton
6

To działa dla mnie:

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

Przykładowe wywołanie:

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

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

Jeśli wolisz, możesz użyć równoważnych długich opcji:

--fixed-strings --quiet --line-regexp --null-data
Steven Penny
źródło
1
Nie działa to z BSD-grep na Macu, ponieważ nie ma --null-data. :(
Czy
4

Pożyczanie od Dennis Williamson „s odpowiedzi , następujące rozwiązanie łączy tablic, muszli bezpieczny cytowanie i wyrażeń regularnych, aby uniknąć konieczności: iteracji po pętli; z zastosowaniem rur lub innych podprocesów; lub używając narzędzi innych niż bash.

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

Powyższy kod działa przy użyciu wyrażeń regularnych Bash w celu dopasowania do skróconej wersji zawartości tablicy. Istnieje sześć ważnych kroków, aby upewnić się, że dopasowanie wyrażenia regularnego nie może zostać oszukane przez sprytne kombinacje wartości w tablicy:

  1. Skonstruować ciąg porównań przy użyciu wbudowanego Bash w printfskorupkach-cytowania %q. Cytowanie powłoki zapewni, że znaki specjalne staną się „bezpieczne dla powłoki” poprzez ucieczkę odwrotnym ukośnikiem \.
  2. Wybierz znak specjalny, który będzie służył jako ogranicznik wartości. Separator MUSI być jednym ze znaków specjalnych, które zostaną użyte podczas ucieczki %q; to jedyny sposób, aby zagwarantować, że wartości w tablicy nie mogą być konstruowane w sprytny sposób, aby oszukać dopasowanie wyrażenia regularnego. Wybieram przecinek, ,ponieważ postać ta jest najbezpieczniejsza, gdy zostanie ewaluowana lub wykorzystana w nieoczekiwany sposób.
  3. Połącz wszystkie elementy tablicy w jeden ciąg, używając dwóch wystąpień znaku specjalnego, aby służyć jako separator. Używając przecinka jako przykładu, użyłem ,,%qjako argumentu do printf. Jest to ważne, ponieważ dwa wystąpienia znaku specjalnego mogą pojawiać się obok siebie tylko wtedy, gdy występują jako separator; wszystkie inne przypadki postaci specjalnej zostaną usunięte.
  4. Dołącz dwa końcowe wystąpienia separatora do ciągu, aby umożliwić dopasowanie do ostatniego elementu tablicy. Dlatego zamiast porównywać z ${array_str}, porównać z ${array_str},,.
  5. Jeśli szukany ciąg docelowy jest dostarczany przez zmienną użytkownika, musisz uciec ze wszystkich wystąpień znaku specjalnego ukośnikiem odwrotnym. W przeciwnym razie dopasowanie wyrażenia regularnego staje się podatne na oszukanie przez sprytnie spreparowane elementy tablicy.
  6. Wykonaj dopasowanie wyrażenia regularnego Bash względem łańcucha.
Dejay Clayton
źródło
Bardzo mądry. Widzę, że można uniknąć większości potencjalnych problemów, ale chciałbym przetestować, czy są jakieś przypadki narożne. Chciałbym też zobaczyć przykład obsługi punktu 5. Coś w tym rodzaju printf -v pattern ',,%q,,' "$user_input"; if [[ "${array_str},," =~ $pattern ]].
Wstrzymano do odwołania.
case "$(printf ,,%q "${haystack[@]}"),," in (*"$(printf ,,%q,, "$needle")"*) true;; (*) false;; esac
Tino,
3

Mały dodatek do odpowiedzi @ ghostdog74 na temat używania caselogiki do sprawdzania, czy tablica zawiera określoną wartość:

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

Lub z extglobwłączoną opcją, możesz to zrobić w następujący sposób:

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

Również możemy to zrobić if instrukcji:

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

dany :

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

następnie prosta kontrola:

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

gdzie

c is element separator
p is regex pattern

(Powodem przypisywania p oddzielnie zamiast używania wyrażenia bezpośrednio w [[]] jest utrzymanie kompatybilności z bash 4)

Beorn Harris
źródło
uwielbiam używanie słowa „prosty” tutaj ... 😂
Christian
2

Łącząc kilka przedstawionych tutaj pomysłów, możesz stworzyć elegancki, jeśli zestawienie bez pętli, które pasuje dokładnie do słów .

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

To nie uruchomi się, wordlub valtylko dopasowania całego słowa. Zepsuje się, jeśli każda wartość tablicy zawiera wiele słów.

Ecker00
źródło
1

Generalnie piszę tego rodzaju narzędzia do działania na nazwie zmiennej, a nie na wartości zmiennej, przede wszystkim dlatego, że bash nie może przekazać zmiennych przez odwołanie.

Oto wersja, która działa z nazwą tablicy:

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
}

W ten sposób przykład pytania staje się:

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

itp.

Barry Kelly
źródło
Czy ktoś może opublikować przykład tego użytego w ciągu if, szczególnie sposób przekazywania w tablicy. Próbuję sprawdzić, czy argument skryptu został przekazany, traktując parametry jako tablicę, ale nie chce działać. params = („$ @”) check = array_contains $ {params} 'SKIPDIRCHECK' if [[$ {check} == 1]]; następnie .... Ale kiedy skrypt działa z argumentem „asas”, ciągle mówi asas: polecenie nie zostało znalezione. : /
Steve Childs
1

Korzystanie grepiprintf

Sformatuj każdy element tablicy w nowej linii, a następnie grepw liniach.

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

Zauważ, że nie ma to problemów z delimetrami i spacjami.

Qwerty
źródło
1

Sprawdzanie jednowierszowe bez „grep” i pętli

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

Podejście to nie korzysta z zewnętrznych narzędzi, takich jak grepani pętle.

To, co się tutaj dzieje, to:

  • używamy symboli zastępczych podciągów do znalezienia naszego elementu w tablicy połączonej w łańcuch;
  • odcinamy możliwe fałszywe alarmy, umieszczając nasz element wyszukiwania między parą ograniczników;
  • używamy znaku niedrukowalnego jako separatora, aby zachować bezpieczeństwo;
  • osiągamy nasz ogranicznik używany również do konkatenacji macierzy przez tymczasowe zastąpienie IFS wartości zmiennej;
  • zmienimy tę IFSwartość na tymczasową, oceniając nasze wyrażenie warunkowe w podpowłoce (w parze nawiasów)
Siergiej Uszakow
źródło
Wyeliminuj dlm. Użyj IFS bezpośrednio.
Robin A. Meade
To najlepsza odpowiedź. Bardzo mi się podobało, napisałem funkcję przy użyciu tej techniki .
Robin A. Meade
1

Korzystanie z rozszerzania parametrów:

$ {parametr: + słowo} Jeśli parametr ma wartość zerową lub jest nieustawiony, nic nie jest podstawiane, w przeciwnym razie zastępowane jest rozwijanie słowa.

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
źródło
${myarray[hello]:+_}działa świetnie dla tablic asocjacyjnych, ale nie dla zwykłych tablic indeksowanych. Pytanie dotyczy znalezienia wartości w obiekcie, a nie sprawdzenia, czy istnieje klucz tablicy asocjacyjnej.
Eric
0

Po udzieleniu odpowiedzi przeczytałem inną odpowiedź, która szczególnie mi się podobała, ale była wadliwa i przegłosowana. Zainspirowałem się i oto dwa nowe podejścia, które według mnie są realne.

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

za pomocą grepi printf:

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

za pomocą for:

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

Aby dodać wyniki not_found, dodaj || <run_your_if_notfound_command_here>

Qwerty
źródło
0

Oto moje zdanie na ten temat.

Wolałbym nie używać basha dla pętli, jeśli mogę tego uniknąć, ponieważ to wymaga czasu. Jeśli coś musi się zapętlić, niech to będzie coś napisanego w języku niższego poziomu niż skrypt powłoki.

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

Działa to poprzez utworzenie tymczasowej tablicy asocjacyjnej, _arrktórej indeksy pochodzą z wartości tablicy wejściowej. (Zauważ, że tablice asocjacyjne są dostępne w wersji bash 4 i nowszych, więc ta funkcja nie będzie działać we wcześniejszych wersjach bash.) Ustawiliśmy, $IFSaby uniknąć podziału słów na białe znaki.

Funkcja nie zawiera wyraźnych pętli, chociaż wewnętrznie przebija się przez tablicę wejściową w celu zapełnienia printf. Format printf używa %qdo zapewnienia, że ​​dane wejściowe są ucieczkowe, dzięki czemu można je bezpiecznie wykorzystać jako klucze tablicy.

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

Zauważ, że wszystko, czego używa ta funkcja, jest wbudowane w bash, więc nie ma żadnych zewnętrznych potoków ciągnących cię w dół, nawet w rozszerzeniu poleceń.

A jeśli nie lubisz używać eval... cóż, możesz zastosować inne podejście. :-)

ghoti
źródło
Co jeśli tablica zawiera nawiasy kwadratowe?
gniourf_gniourf
@gniourf_gniourf - wydaje się być w porządku, jeśli nawiasy kwadratowe są zrównoważone, ale widzę problem, jeśli twoja tablica zawiera wartości z niesymetrycznymi nawiasami kwadratowymi. W takim przypadku przywołam evalinstrukcję na końcu odpowiedzi. :)
ghoti
To nie tak, że mi się nie podoba eval(nie mam nic przeciwko temu, w przeciwieństwie do większości ludzi, którzy płaczą, evalto zło, przeważnie nie rozumiejąc, co jest w tym złego). Tylko, że twoje polecenie jest złamane. Może %qzamiast %sbyłoby lepiej.
gniourf_gniourf
1
@gniourf_gniourf: Miałem na myśli tylko fragment „innego podejścia” (i jestem całkowicie z tobą eval, oczywiście), ale masz całkowitą rację, %qwydaje się pomagać, nie psując niczego, co widzę. (Nie zdawałem sobie sprawy, że% q również ucieknie nawiasom kwadratowym.) Kolejny problem, który widziałem i naprawiłem, dotyczył białych znaków. Z a=(one "two " three), podobnie jak w przypadku Keegana: nie tylko array_contains a "two "otrzymał fałszywy negatyw, ale array_contains a tworównież fałszywie pozytywny. Łatwo to naprawić, ustawiając IFS.
ghoti
Odnośnie białych znaków, czyż nie dlatego, że brakuje cytatów? łamie się także z globalnymi postaciami. Myślę, że zamiast tego chcesz: eval _arr=( $(eval printf '[%q]="1"\ ' "\"\${$1[@]}\"") )i możesz porzucić local IFS=. Nadal występuje problem z pustymi polami w tablicy, ponieważ Bash odmówi utworzenia pustego klucza w tablicy asocjacyjnej. Szybki hacky sposób, aby to naprawić, polega na przygotowaniu fikcyjnego bohatera, powiedz x: eval _arr=( $(eval printf '[x%q]="1"\ ' "\"\${$1[@]}\"") )i return $(( 1 - 0${_arr[x$2]} )).
gniourf_gniourf
-1

Moja wersja techniki wyrażeń regularnych, która została już zasugerowana:

values=(foo bar)
requestedValue=bar

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

To, co się tutaj dzieje, polega na tym, że rozszerzasz całą tablicę obsługiwanych wartości na słowa i przygotowujesz określony ciąg, w tym przypadku „X-”, do każdej z nich, i robisz to samo z żądaną wartością. Jeśli ten rzeczywiście jest zawarty w tablicy, wynikowy ciąg znaków będzie co najwyżej pasował do jednego z powstałych tokenów lub wcale nie będzie odwrotnie. W tym ostatnim przypadku || operator uruchamia się i wiesz, że masz do czynienia z nieobsługiwaną wartością. Przed tym wszystkim żądana wartość jest usuwana ze wszystkich wiodących i końcowych białych znaków za pomocą standardowych operacji na łańcuchach powłoki.

Uważam, że jest czysty i elegancki, choć nie jestem zbyt pewny, jak skuteczny może być, jeśli Twój zestaw obsługiwanych wartości jest szczególnie duży.

jmpp
źródło
-1

Oto moje podejście do tego problemu. Oto krótka wersja:

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

I długa wersja, która moim zdaniem jest znacznie łatwiejsza dla oczu.

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

Przykłady:

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
źródło
Od tak dawna nie używam basha, że ​​mam trudności ze zrozumieniem odpowiedzi, a nawet tego, co sam napisałem :) Nie mogę uwierzyć, że to pytanie wciąż jest aktywne po tak długim czasie :)
Paolo Tedesco
Co test_arr=("hello" "world" "two words")?
Qwerty
-1

Miałem przypadek, że musiałem sprawdzić, czy identyfikator znajduje się na liście identyfikatorów wygenerowanych przez inny skrypt / polecenie. Dla mnie zadziałało:

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

Możesz także skrócić / skompresować w następujący sposób:

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

W moim przypadku uruchomiłem jq, aby przefiltrować JSON pod kątem listy identyfikatorów i musiałem później sprawdzić, czy mój identyfikator znajduje się na tej liście i to działało najlepiej dla mnie. Nie będzie działać w przypadku ręcznie tworzonych tablic typu, LIST=("1" "2" "4")ale w przypadku danych wyjściowych skryptu oddzielonych znakiem nowej linii.


PS .: nie mogę skomentować odpowiedzi, ponieważ jestem stosunkowo nowy ...

E. Körner
źródło
-2

Poniższy kod sprawdza, czy dana wartość znajduje się w tablicy i zwraca jej zerowe przesunięcie:

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

Dopasowanie jest wykonywane dla pełnych wartości, dlatego ustawienie WARTOŚĆ = „trzy” nie będzie pasować.

Sven Rieke
źródło
-2

Warto to zbadać, jeśli nie chcesz iterować:

#!/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

Fragment dostosowany z: http://www.thegeekstuff.com/2010/06/bash-array-tutorial/ Myślę, że jest całkiem sprytny.

EDYCJA: Prawdopodobnie możesz po prostu zrobić:

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

Ale to ostatnie działa tylko wtedy, gdy tablica zawiera unikalne wartości. Szukanie 1 w „143” da fałszywie dodatni, metinks.

Sigg3.net
źródło
-2

Trochę późno, ale możesz użyć tego:

#!/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
źródło
-2

Wymyśliłem ten, który okazuje się działać tylko w Zsh, ale myślę, że ogólne podejście jest dobre.

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

Wyciągasz wzór z każdego elementu tylko wtedy, gdy zaczyna się ${arr[@]/#pattern/}lub kończy ${arr[@]/%pattern/}. Te dwie substytucje działają w trybie bash, ale oba jednocześnie${arr[@]/#%pattern/} działają tylko w Zsh.

Jeśli zmodyfikowana tablica jest równa oryginałowi, to nie zawiera elementu.

Edytować:

Ten działa w bash:

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

Po podstawieniu porównuje długość obu tablic. Oczywiście, jeśli tablica zawiera element, podstawienie całkowicie go usunie, a liczba będzie się różnić.

spelufo
źródło