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?

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)

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 .


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

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

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


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

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.