Перевірте, чи є елемент в масиві в bash


17

Чи є приємний спосіб перевірити, чи має масив елемент bash (краще, ніж прокручувати)?

Як варіант, чи є інший спосіб перевірити, чи число чи рядок дорівнює будь-якому з набору заздалегідь визначених констант?

Відповіді:


24

У Bash 4 можна використовувати асоціативні масиви:

# 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

Щоб налаштувати масив спочатку, ви також можете виконати прямі завдання:

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

або таким чином:

array=([foo]=1 [bar]=1 [baz]=1)

Насправді тест [[]] не працює, якщо значення порожнє. Наприклад, "array ['test'] = ''". У цьому випадку ключ 'test' існує, і ви можете побачити його у списку з $ {! Array [@]}, але "[[$ {array ['test']}]]]; echo $?" відлуння 1, а не 0.
haridsv

1
${array[$test1]}це просто, але є проблема: він не працюватиме, якщо ви використовуєте set -uв своїх сценаріях (що рекомендується), оскільки ви отримаєте "незв'язану змінну".
tokland

@tokland: Хто рекомендує це? Я, звичайно, ні.
Призупинено до подальшого повідомлення.

@DennisWilliamson: Гаразд, деякі люди рекомендують це, але я думаю, було б непогано мати рішення, яке працює незалежно від значення цих прапорів.
tokland


10

Це старе питання, але я думаю , що це найпростіше рішення ще не з'явилося: test ${array[key]+_}. Приклад:

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"

Виходи:

a is set
b is set

Щоб побачити, як це працює, перевірте це .


2
Інформаційний посібник рекомендує вам використовувати, envщоб уникнути неясностей у псевдонімах, прогах та інших функціях, які, можливо, прийняли назву "тест". Як і вище env test ${xs[a]+_} && echo "a is set". Ви також можете отримати цю функціональність, скориставшись подвійними дужками, той самий трюк, як і перевірка нуля:[[ ! -z "${xs[b]+_}" ]] && echo "b is set"
A.Danischewski

Також можна скористатися ще простішим[[ ${xs[b]+set} ]]
Арне Л.

5

Існує спосіб перевірити, чи існує елемент асоціативного масиву (не встановлений), він відрізняється від порожнього:

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

Потім використовуйте його:

declare -A assoc
KEY="key"
isNotSet assoc[${KEY}]
if [ $? -ne 0 ]
then
  echo "${KEY} is not set."
fi

лише зауваження: заявити -А не працює на bash 3.2.39 (debian lenny), але він працює на bash 4.1.5 (debian стиск)
Paweł Polewicz

Асоціативні масиви були введені в Bash 4.
Дієго Ф. Дуран

1
зауважимо, що if ! some_check then return 1= some_check. Отже: isNotSet() { [[ ... ]] }. Ознайомтесь із моїм рішенням нижче, це можна зробити простою перевіркою.
tokland

3

Ви можете побачити, чи є запис, перенісши вміст масиву в режим grep.

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

Ви також можете отримати індекс запису з grep -n, який повертає номер рядка відповідності (не забудьте відняти 1 для отримання нульового індексу). Це буде досить швидко, за винятком дуже великих масивів.

# 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

пояснення:

  • $( ... ) це те саме, що використовувати задні посилання для збору результатів команди в змінну
  • printf виводить мій дані по одному елементу на рядок
  • (усі необхідні цитати, а не @замість *. цього уникає поділу "привіт світу" на 2 рядки)
  • grepшукає точний рядок: ^і $співпадає початок і кінець рядка
  • grep -n повертає рядок № у вигляді 4: привіт світ
  • grep -m 1 знаходить лише перший матч
  • cut витягує лише номер рядка
  • відняти 1 від повернутого номера рядка.

Можна, звичайно, скласти віднімання в команду. Але потім тестуйте -1 на відсутність:

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

if [[ $ix == -1 ]]; then echo missing; else ... fi
  • $(( ... )) робить цілу арифметику

1

Я не думаю , що ви можете зробити це правильно , без зациклення , якщо ви не дуже обмежених даних в масиві.

Ось один простий варіант, це правильно би сказав, що "Super User"існує в масиві. Але це також би сказало, що "uper Use"є в масиві.

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

Проблема полягає в тому, що немає простого способу додати якорі (що я можу придумати), окрім прокручування масиву. Якщо ви не можете додати їх перед тим, як помістити їх у масив ...


Це приємне рішення, коли константи буквено-цифрові, хоча (з grep "\b$FINDME\b"). Можливо, це могло б працювати з не алфавітно-цифровими константами, у яких немає пробілів, з "(^| )$FINDME(\$| )"(або чимось подібним ... я ніколи не зміг дізнатися, який аромат використовує regexp grep.)
Тгр

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
Чи можете ви детальніше пояснити, чому ви пропонуєте такий підхід? ОП запитав, чи є спосіб це зробити, не перебираючи масив , для чого ви робите in_array. Ура
бертіб

Ну, принаймні, цей цикл є капсульованим у функції, яка може бути досить хорошою для багатьох випадків (з невеликими наборами даних) і не вимагає bash 4+. Напевно, ${ARRAY[@]}слід використовувати.
Тобіас
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.