Sprawdź, czy element znajduje się w tablicy w bash

17

Czy istnieje dobry sposób sprawdzenia, czy tablica zawiera element w bashu (lepszy niż zapętlanie)?

Alternatywnie, czy istnieje inny sposób sprawdzenia, czy liczba lub ciąg znaków jest równy dowolnemu zestawowi predefiniowanych stałych?

Tgr
źródło

Odpowiedzi:

24

W Bash 4 możesz używać tablic asocjacyjnych:

# 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

Aby wstępnie skonfigurować tablicę, możesz również wykonać bezpośrednie przypisania:

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

lub w ten sposób:

array=([foo]=1 [bar]=1 [baz]=1)
Wstrzymano do odwołania.
źródło
W rzeczywistości test [[]] nie działa w przypadku, gdy wartość jest pusta. Np. „Array ['test'] = ''”. W tym przypadku istnieje klucz „test” i można go zobaczyć na liście z $ {! Array [@]}, ale „[[$ {array ['test']}]]; echo $?” echo 1, a nie 0.
haridsv
1
${array[$test1]}jest prosty, ale ma problem: nie zadziała, jeśli użyjesz go set -uw swoich skryptach (co jest zalecane), ponieważ otrzymasz „niezwiązaną zmienną”.
tokland
@tokland: Kto to poleca? Na pewno nie.
Wstrzymano do odwołania.
@DennisWilliamson: Ok, niektórzy ludzie to polecają, ale myślę, że fajnie byłoby mieć rozwiązanie, które działa niezależnie od wartości tych flag.
tokland
10

To stare pytanie, ale myślę, że to, co jest najprostsze rozwiązanie jeszcze nie pojawił: test ${array[key]+_}. Przykład:

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"

Wyjścia:

a is set
b is set

Aby zobaczyć, jak to działa, sprawdź to .

tokland
źródło
2
Podręcznik informacyjny zaleca korzystanie z niego, envaby uniknąć dwuznaczności w aliasach, progach i innych funkcjach, które mogły przyjąć nazwę „test”. Jak wyżej env test ${xs[a]+_} && echo "a is set". Możesz także uzyskać tę funkcjonalność za pomocą podwójnych nawiasów, ta sama sztuczka, a następnie sprawdzenie, czy null:[[ ! -z "${xs[b]+_}" ]] && echo "b is set"
A.Danischewski
Możesz także użyć jeszcze prostszego[[ ${xs[b]+set} ]]
Arne L.
5

Istnieje sposób sprawdzenia, czy istnieje element tablicy asocjacyjnej (nie jest ustawiony), różni się on od pustego:

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

Następnie użyj go:

declare -A assoc
KEY="key"
isNotSet assoc[${KEY}]
if [ $? -ne 0 ]
then
  echo "${KEY} is not set."
fi
Diego F. Durán
źródło
tylko uwaga: deklaruj -A nie działa na bash 3.2.39 (debian lenny), ale działa na bash 4.1.5 (debian squeeze)
Paweł Polewicz
Tablice asocjacyjne zostały wprowadzone w Bash 4.
Diego F. Durán
1
zauważ, że if ! some_check then return 1= some_check. Tak: isNotSet() { [[ ... ]] }. Sprawdź moje rozwiązanie poniżej, możesz to zrobić w prosty sposób.
tokland
3

Możesz sprawdzić, czy pozycja jest obecna, przesyłając potokiem zawartość tablicy do grep.

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

Możesz także uzyskać indeks pozycji za pomocą grep -n, który zwraca numer linii dopasowania (pamiętaj, aby odjąć 1, aby uzyskać indeks zerowy). Będzie to dość szybkie, z wyjątkiem bardzo dużych tablic.

# 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

wyjaśnienie:

  • $( ... ) to to samo, co użycie backsticksa do przechwytywania wyniku polecenia w zmiennej
  • printf wypisuje moje dane po jednym elemencie w wierszu
  • (wszystkie cytaty są konieczne, a @zamiast *. tego unika się podziału „witaj świecie” na 2 linie)
  • grepszuka dokładnego ciągu: ^i $dopasowuje początek i koniec wiersza
  • grep -n zwraca wiersz nr, w postaci 4: witaj świecie
  • grep -m 1 znajduje tylko pierwszy mecz
  • cut wyodrębnia tylko numer wiersza
  • odejmij 1 od zwróconego numeru wiersza.

Możesz oczywiście złożyć odejmowanie do polecenia. Ale następnie sprawdź brak -1:

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

if [[ $ix == -1 ]]; then echo missing; else ... fi
  • $(( ... )) robi arytmetykę liczb całkowitych
Kane
źródło
1

Nie sądzę, że możesz to zrobić poprawnie bez zapętlania, chyba że masz bardzo ograniczone dane w tablicy.

Oto jeden prosty wariant, to poprawnie powiedziałoby, że "Super User"istnieje w tablicy. Ale powiedziałoby to również, że "uper Use"jest w tablicy.

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

Problem polega na tym, że nie ma łatwego sposobu na dodanie kotwic (które mogę wymyślić) poza zapętlaniem tablicy. Chyba że możesz dodać je przed umieszczeniem ich w tablicy ...

Nifle
źródło
Jest to dobre rozwiązanie, gdy stałe są alfanumeryczne (chociaż grep "\b$FINDME\b"). Prawdopodobnie mógłby pracować ze stałymi niealfanumerycznymi, które nie mają spacji, z "(^| )$FINDME(\$| )"(lub coś w tym rodzaju ... Nigdy nie byłem w stanie dowiedzieć się, jakiego smaku używa wyrażenie regularne grep.)
wyrażenie regularne Tgr
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
źródło
1
Czy możesz wyjaśnić, dlaczego sugerujesz takie podejście? OP zapytał, czy istnieje sposób, aby to zrobić bez zapętlania tablicy , co robisz in_array. Na zdrowie
bertieb
Cóż, przynajmniej ta pętla jest zawarta w funkcji, która może być wystarczająca w wielu przypadkach (przy małych zestawach danych) i nie wymaga bash 4+. Prawdopodobnie ${ARRAY[@]}należy użyć.
Tobias