Как проверить, существует ли переменная в списке в BASH

138

Я пытаюсь написать скрипт на bash, который проверяет правильность ввода пользователя.
Я хочу сопоставить вход (скажем, переменную x) со списком допустимых значений.

что я придумал на данный момент:

for item in $list
do
    if [ "$x" == "$item" ]; then
        echo "In the list"
        exit
    fi
done

У меня вопрос: есть ли более простой способ сделать это,
что-то вроде a list.contains(x)для большинства языков программирования?

Дополнение:
Скажите, что список:

list="11 22 33"

Мой код будет отображать сообщение только для этих значений, так listкак он обрабатывается как массив, а не как строка, все манипуляции со строками будут проверены, 1пока я не захочу, чтобы он потерпел неудачу.

Офир Фарчи
источник

Ответы:

143
[[ $list =~ (^|[[:space:]])$x($|[[:space:]]) ]] && echo 'yes' || echo 'no'

или создайте функцию:

contains() {
    [[ $1 =~ (^|[[:space:]])$2($|[[:space:]]) ]] && exit(0) || exit(1)
}

использовать это:

contains aList anItem
echo $? # 0: match, 1: failed
chemila
источник
37
должно быть[[ $list =~ (^| )$x($| ) ]] && echo 'yes' || echo 'no'
Матвей Аксенов
12
Может давать ложное срабатывание, если пользовательский ввод содержит специальные символы регулярного выражения, напримерx=.
glenn jackman
4
Это не даст ложных срабатываний / негативов contains () { [[ "$1" =~ (^|[[:space:]])"$2"($|[[:space:]]) ]]; }.
скозин
6
более лаконичное решение: [[ " $list " =~ " $x " ]] && echo 'yes' || echo 'no'. это правильно, если предположить, что пробел является разделителем и $xне содержит пробела
Tianren Liu
2
Я думаю, что лучше использовать функцию «в», isIn()чтобы вы могли сначала написать элемент в параметрах. Также вы можете использовать echoчто-то вместо exitэтого: [[ $2 =~ (^|[[:space:]])$1($|[[:space:]]) ]] && echo 1 || echo 0Таким образом, вы можете использовать эту функцию следующим образом:result=$(isIn "-t" "-o -t 45") && echo $result
hayj
34

Матвей прав, но вы должны заключить в кавычки $ x и рассмотреть любые «пробелы» (например, новую строку) с

[[ $list =~ (^|[[:space:]])"$x"($|[[:space:]]) ]] && echo 'yes' || echo 'no' 

так что

# list_include_item "10 11 12" "2"
function list_include_item {
  local list="$1"
  local item="$2"
  if [[ $list =~ (^|[[:space:]])"$item"($|[[:space:]]) ]] ; then
    # yes, list include item
    result=0
  else
    result=1
  fi
  return $result
}

конец тогда

`list_include_item "10 11 12" "12"`  && echo "yes" || echo "no"

или

if `list_include_item "10 11 12" "1"` ; then
  echo "yes"
else 
  echo "no"
fi

Обратите внимание, что вы должны использовать ""в случае переменных:

`list_include_item "$my_list" "$my_item"`  && echo "yes" || echo "no"
Oriettaxx
источник
3
Это решение работает, даже если $itemсодержит специальные символы, например .(но $listпеременная, вероятно, должна быть заключена в кавычки внутри теста). А функция может быть определена еще проще: contains () { [[ "$1" =~ (^|[[:space:]])"$2"($|[[:space:]]) ]]; }.
скозин
не работает в таких случаях, как: list="aa bb xx cc ff"иx="aa bb"
creativeChips
Привет. Я пытаюсь изучить основы bashscript, и я хотел знать, как вы можете разобрать строку списка в список, и вы делаете проверку существующих в этом списке. Я могу понять, что вы разделены, используя пробел, но я не мог полностью понять выражение. Можете ли вы предоставить мне слово для Google основу этого выражения: $list =~ (^|[[:space:]])"$item"($|[[:space:]]). Или, если у вас есть время, я был бы рад услышать ваше объяснение этого выражения. Примечание: я предполагаю, что это выражение регулярного выражения (начинается с ^ и т. Д.), Но я не знаю, что означает = ~. Так что я хотел бы общее объяснение: P
Али Йылмаз
28

ИМХО самое простое решение - добавить и добавить исходную строку с пробелом и проверить регулярное выражение с помощью [[ ]]

haystack='foo bar'
needle='bar'

if [[ " $haystack " =~ .*\ $needle\ .* ]]; then
    ...
fi

это не будет ложно положительным для значений со значениями, содержащими иглу в качестве подстроки, например со стогом сена foo barbaz.

(Концепция бесстыдно похищена из hasClass()JQuery's-метода)

blaimi
источник
4
если разделитель не является пробелом, решение более очевидно. это также может быть сделано без регулярных выражений: haystack="foo:bar"и[[ ":$haystack:" = *:$needle:* ]]
phiphi
25

как насчет

echo $list | grep -w $x

Вы можете проверить вывод или $?вышеприведенную строку, чтобы принять решение.

grep -w проверяет целые шаблоны слов.

Кент
источник
1
не будет ли это валидировать «val», если «value» действительно (то есть в списке), так как это его подстрока
Ofir Farchy
Да, как и принятый ответ. @glennjackman дает рабочее решение.
f.ardelian
3
вы можете использовать grep -q, чтобы заставить grep быть спокойным
Амир,
это также будет принимать частичные совпадения, которые могут не соответствовать
желанию
3
@carnicer тогда просто используйте grep -w $ x, чтобы получить точное совпадение
user1297406
18

Вы также можете использовать (* подстановочные знаки) вне оператора case, если вы используете двойные скобки:

string='My string';

if [[ $string == *My* ]]
then
echo "It's there!";
fi
Митхун Сасидхаран
источник
3
Простой и элегантный, +1
Жоау Перейра
14
Это даст ложное срабатывание, если «My» является подстрокой другой строки, например string = 'MyCommand string'.
Люк Ли
Стоит отметить, что подстановочные знаки ( *My*) должны быть в правой части теста.
Джейкоб Ванус
8

Если ваш список значений должен быть жестко запрограммирован в скрипте, его довольно просто протестировать case. Вот краткий пример, который вы можете адаптировать к вашим требованиям:

for item in $list
do
    case "$x" in
      item1|item2)
        echo "In the list"
        ;;
      not_an_item)
        echo "Error" >&2
        exit 1
        ;;
    esac
done

Если список является переменной массива во время выполнения, возможно, лучше подойдет один из других ответов.

Тоби Спейт
источник
7

Если список исправлен в скрипте, мне больше всего нравится следующее:

validate() {
    grep -F -q -x "$1" <<EOF
item 1
item 2
item 3
EOF
}

Затем используйте validate "$x"для проверки, если $xэто разрешено.

Если вы хотите использовать одну строку и вам не нужны пробелы в именах элементов, вы можете использовать это (обратите внимание, -wвместо -x):

validate() { echo "11 22 33" | grep -F -q -w "$1"; }

Ноты:

  • Это POSIX sh совместимый.
  • validateвовсе не принимает подстроки (удалить-x опцию Grep , если вы хотите , что).
  • validateинтерпретирует его аргумент как фиксированную строку, а не как регулярное выражение ( -Fесли хотите, удалите опцию для grep).

Пример кода для осуществления функции:

for x in "item 1" "item2" "item 3" "3" "*"; do
    echo -n "'$x' is "
    validate "$x" && echo "valid" || echo "invalid"
done
Сёрен Лёвборг
источник
6

Рассмотрим использование ключей ассоциативных массивов . Я бы предположил, что это превосходит как регулярное выражение / сопоставление с образцом, так и циклы, хотя я не профилировал это.

declare -A list=( [one]=1 [two]=two [three]='any non-empty value' )
for value in one two three four
do
    echo -n "$value is "
    # a missing key expands to the null string, 
    # and we've set each interesting key to a non-empty value
    [[ -z "${list[$value]}" ]] && echo -n '*not* '
    echo "a member of ( ${!list[*]} )"
done

Вывод:

one is a member of ( one two three )
two is a member of ( one two three )
three is a member of ( one two three )
four is *not* a member of ( one two three )
RubyTuesdayDONO
источник
2
... и вы можете упростить эту замену (а не полагаться на echo -n) с творческим использованием параметров: do is="${list[$value]+is }"; echo "$value ${is:-is *not* }a member of ( ${!list[*]} )"; done.
Тоби Спейт
Есть ли простой способ создать такой массив, если у меня есть только (длинный) список вроде `list =" one two three xyz ... "?
Отт Тоомет
5

Я считаю, что проще использовать форму, echo $LIST | xargs -n1 echo | grep $VALUEкак показано ниже:

LIST="ITEM1 ITEM2"
VALUE="ITEM1"
if [ -n "`echo $LIST | xargs -n1 echo | grep -e \"^$VALUE`$\" ]; then
    ...
fi

Это работает для списка, разделенного пробелами, но вы можете адаптировать его к любому другому разделителю (например :), выполнив следующее:

LIST="ITEM1:ITEM2"
VALUE="ITEM1"
if [ -n "`echo $LIST | sed 's|:|\\n|g' | grep -e \"^$VALUE`$\"`" ]; then
   ...
fi

Обратите внимание, что "для работы теста требуется.

Себастьян Пьер
источник
Это заставит LIST="SOMEITEM1 ITEM2"возвращать истину, даже если ITEM1ее нет
Ofir Farchy
Хороший улов, я обновил пример с grep -e, чтобы исключить частичное совпадение.
Себастьян Пьер
Я полагаю, что в конце утверждения «если» есть лишний символ. Правильная форма: [-n "` echo $ LIST | xargs -n1 echo | grep -e \ "^ $ VALUE $ \"]
Elad Tabak
3

Думал, я бы добавил свое решение в список.

# Checks if element "$1" is in array "$2"
# @NOTE:
#   Be sure that array is passed in the form:
#       "${ARR[@]}"
elementIn () {
    # shopt -s nocasematch # Can be useful to disable case-matching
    local e
    for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 0; done
    return 1
}

# Usage:
list=(11 22 33)
item=22

if elementIn "$item" "${list[@]}"; then
    echo TRUE;
else
    echo FALSE
fi
# TRUE

item=44
elementIn $item "${list[@]}" && echo TRUE || echo FALSE
# FALSE
SamWN
источник
1

Примеры

$ in_list super test me out
NO

$ in_list "super dude" test me out
NO

$ in_list "super dude" test me "super dude"
YES

# How to use in another script
if [ $(in_list $1 OPTION1 OPTION2) == "NO" ]
then
  echo "UNKNOWN type for param 1: Should be OPTION1 or OPTION2"
  exit;
fi

in_list

function show_help()
{
  IT=$(CAT <<EOF

  usage: SEARCH_FOR {ITEM1} {ITEM2} {ITEM3} ...

  e.g. 

  a b c d                    -> NO
  a b a d                    -> YES
  "test me" how "test me"    -> YES

  )
  echo "$IT"
  exit
}

if [ "$1" == "help" ]
then
  show_help
fi

if [ "$#" -eq 0 ]; then
  show_help
fi

SEARCH_FOR=$1
shift;

for ITEM in "$@"
do
  if [ "$SEARCH_FOR" == "$ITEM" ]
  then
    echo "YES"
    exit;
  fi
done

echo "NO"
Брэд Паркс
источник
1

Предполагая, что переменная TARGET может быть только 'биномиальной' или 'регрессией', тогда будет сделано следующее:

# Check for modeling types known to this script
if [ $( echo "${TARGET}" | egrep -c "^(binomial|regression)$" ) -eq 0 ]; then
    echo "This scoring program can only handle 'binomial' and 'regression' methods now." >&2
    usage
fi

Вы можете добавить больше строк в список, разделив их символом | (труба) персонажа.

Преимущество использования egrep заключается в том, что вы можете легко добавить нечувствительность к регистру (-i) или проверить более сложные сценарии с помощью регулярного выражения.

Tagar
источник
1

Это почти ваше оригинальное предложение, но почти 1-лайнер. Не так сложно, как другие правильные ответы, и не так, в зависимости от версий bash (может работать со старыми bash).

OK=0 ; MP_FLAVOURS="vanilla lemon hazelnut straciatella"
for FLAV in $MP_FLAVOURS ; do [ $FLAV == $FLAVOR ] && { OK=1 ; break; } ; done
[ $OK -eq 0 ] && { echo "$FLAVOR not a valid value ($MP_FLAVOURS)" ; exit 1 ; }

Я предполагаю, что мое предложение еще можно улучшить, как по длине, так и по стилю.

Carnicer
источник
0

Если это не слишком долго; Вы можете просто связать их между равенствами в логическом ИЛИ-сравнении, например.

if [ $ITEM == "item1" -o $ITEM == "item2" -o $ITEM == "item3" ]; then
    echo In the list
fi 

У меня была именно эта проблема, и, хотя вышесказанное выглядит ужасно, происходящее более очевидно, чем другие обобщенные решения.

Келли Трин
источник