Як перевірити, чи існує змінна в списку в BASH


137

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

що я придумав на даний момент, це:

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

Моє запитання, чи існує простіший спосіб зробити це,
щось подібне list.contains(x)до більшості мов програмування.

Доповнення:
Скажімо, список:

list="11 22 33"

мій код буде повторювати повідомлення лише для цих значень, оскільки listтрактується як масив, а не рядок, усі рядкові маніпуляції будуть перевірені, 1тоді як я хотів би, щоб це не було.

Відповіді:


142
[[ $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

37
має бути[[ $list =~ (^| )$x($| ) ]] && echo 'yes' || echo 'no'
Матвій Аксьонов

12
Може дати хибний позитив, якщо вхід користувача містить спеціальні символи регулярного вираження, наприкладx=.
glenn jackman

4
Це не дасть ніяких помилкових спрацьовувань / негативів: contains () { [[ "$1" =~ (^|[[:space:]])"$2"($|[[:space:]]) ]]; }.
skozin

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"

3
Це рішення працює, навіть якщо $itemмістить спеціальні символи, як-от .(але, $listнапевно, змінну потрібно навести у тесті). А функція може бути визначена ще простіше: contains () { [[ "$1" =~ (^|[[:space:]])"$2"($|[[:space:]]) ]]; }.
skozin

не працює у таких випадках: list="aa bb xx cc ff"таx="aa bb"
creativeChips

Привіт. Я намагаюся вивчити основи bashscript, і мені хотілося знати, як ви можете розібрати рядок списку в список, і ви зробите перевірку в цьому списку. Я можу зрозуміти, що ви розділилися за допомогою простору, але я не зміг повністю зрозуміти вираз. Можете чи ви надати мені слово для Google основу цього виразу: $list =~ (^|[[:space:]])"$item"($|[[:space:]]). Або, якщо у вас є час, я би радий почути ваше пояснення цього виразу. Примітка. Я думаю, що це вираження регулярного вираження (починається з ^ тощо), але я не знаю, що означає = ~. Тож я хотів би загальне пояснення: П
Алі Йылмаз

28

Найпростішим рішенням IMHO є додавання та додавання початкового рядка до пробілу та перевірка відповідності регулярного вираження [[ ]]

haystack='foo bar'
needle='bar'

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

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

(Концепція безсоромно викрадена у формі hasClass()-методу JQuery's )


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 бути тихим
Амір

це також сприйме часткові збіги, що може бути не тим, що хоче користувач
carnicer

3
@carnicer тоді просто використовуйте grep -w $ x, щоб мати точну відповідність
user1297406

18

Ви можете використовувати (* підмітні знаки) і поза заявою, якщо ви використовуєте подвійні дужки:

string='My string';

if [[ $string == *My* ]]
then
echo "It's there!";
fi

3
Простий та елегантний, +1
Жоао Перейра

14
Це дасть помилковий позитивний результат, якщо "Мій" є підрядком іншого рядка, наприклад, string = "рядок MyCommand".
Люк Лі

Варто зауважити, що подвійні знаки ( *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"; }

Примітки:

  • Це shсумісно з POSIX .
  • 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 )

2
... і ви можете спростити цю заміну (а не покладатися на echo -n) з творчим використанням параметрів: do is="${list[$value]+is }"; echo "$value ${is:-is *not* }a member of ( ${!list[*]} )"; done.
Toby Speight

Чи є простий спосіб створити такий масив, якщо у мене є лише (довгий) список на зразок `list =" один два три xyz ... "?
Ott Toomet

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

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) або перевірити складніші сценарії з регулярним виразом.


1

Це майже ваша оригінальна пропозиція, але майже 1-лайнер. Не так складно, як інші правильні відповіді, і не так залежно від версій 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 ; }

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


0

Якщо це не надто довго; ви можете просто вписати їх між рівностями в ході логічного АБО порівняння.

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

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.