Проверьте, содержит ли массив Bash значение - PullRequest
353 голосов
/ 10 сентября 2010

В Bash, какой самый простой способ проверить, содержит ли массив определенное значение?

Редактировать : С помощью ответов и комментариев после некоторого тестирования я придумал следующее:

function contains() {
    local n=$#
    local value=${!n}
    for ((i=1;i < $#;i++)) {
        if [ "${!i}" == "${value}" ]; then
            echo "y"
            return 0
        fi
    }
    echo "n"
    return 1
}

A=("one" "two" "three four")
if [ $(contains "${A[@]}" "one") == "y" ]; then
    echo "contains one"
fi
if [ $(contains "${A[@]}" "three") == "y" ]; then
    echo "contains three"
fi

Не уверен,лучшее решение, но, похоже, работает.

Ответы [ 32 ]

1 голос
/ 01 ноября 2016

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

array=("word" "two words") # let's look for "two words"

с использованием grep и printf:

(printf '%s\n' "${array[@]}" | grep -x -q "two words") && <run_your_if_found_command_here>

с использованием for:

(for e in "${array[@]}"; do [[ "$e" == "two words" ]] && exit 0; done; exit 1) && <run_your_if_found_command_here>

Для не найденных результатов добавьте || <run_your_if_notfound_command_here>

1 голос
/ 24 ноября 2016

Вот мой взгляд на это.

Я бы предпочел не использовать цикл для bash, если я могу избежать этого, так как для этого требуется время. Если что-то должно зацикливаться, пусть это будет что-то написанное на языке более низкого уровня, чем сценарий оболочки.

function array_contains { # arrayname value
  local -A _arr=()
  local IFS=
  eval _arr=( $(eval printf '[%q]="1"\ ' "\${$1[@]}") )
  return $(( 1 - 0${_arr[$2]} ))
}

Это работает путем создания временного ассоциативного массива, _arr, индексы которого получены из значений входного массива. (Обратите внимание, что ассоциативные массивы доступны в bash 4 и выше, поэтому эта функция не будет работать в более ранних версиях bash.) Мы устанавливаем $IFS, чтобы избежать разбиения слов в пробеле.

Функция не содержит явных циклов, хотя внутренняя bash проходит через входной массив для заполнения printf. Формат printf использует %q, чтобы гарантировать, что входные данные экранированы так, что их можно безопасно использовать в качестве ключей массива.

$ a=("one two" three four)
$ array_contains a three && echo BOOYA
BOOYA
$ array_contains a two && echo FAIL
$

Обратите внимание, что все, что использует эта функция, встроено в bash, поэтому нет никаких внешних каналов, тянущих вас вниз, даже в расширении команды.

И если вам не нравится eval ... ну, вы можете использовать другой подход. : -)

0 голосов
/ 27 июля 2015

Хотя здесь было несколько хороших и полезных ответов, я не нашел такого, который казался бы правильным сочетанием производительности, кроссплатформенности и надежности; поэтому я хотел бы поделиться решением, которое я написал для моего кода:

#!/bin/bash

# array_contains "$needle" "${haystack[@]}"
#
# Returns 0 if an item ($1) is contained in an array ($@).
#
# Developer note:
#    The use of a delimiter here leaves something to be desired. The ideal
#    method seems to be to use `grep` with --line-regexp and --null-data, but
#    Mac/BSD grep doesn't support --line-regexp.
function array_contains()
{
    # Extract and remove the needle from $@.
    local needle="$1"
    shift

    # Separates strings in the array for matching. Must be extremely-unlikely
    # to appear in the input array or the needle.
    local delimiter='#!-\8/-!#'

    # Create a string with containing every (delimited) element in the array,
    # and search it for the needle with grep in fixed-string mode.
    if printf "${delimiter}%s${delimiter}" "$@" | \
        grep --fixed-strings --quiet "${delimiter}${needle}${delimiter}"; then
        return 0
    fi

    return 1
}
0 голосов
/ 01 июля 2013

Это может стоить изучить, если вы не хотите повторять:

#!/bin/bash
myarray=("one" "two" "three");
wanted="two"
if `echo ${myarray[@]/"$wanted"/"WAS_FOUND"} | grep -q "WAS_FOUND" ` ; then
 echo "Value was found"
fi
exit

Фрагмент адаптирован из: http://www.thegeekstuff.com/2010/06/bash-array-tutorial/ Я думаю, что это довольно умно.

РЕДАКТИРОВАТЬ: Вы, вероятно, могли бы просто сделать:

if `echo ${myarray[@]} | grep -q "$wanted"` ; then
echo "Value was found"
fi

Но последнее работает, только если массив содержит уникальные значения.Поиск 1 в «143» даст ложное срабатывание, метинкс.

0 голосов
/ 15 июля 2014

Сочетание ответов Беорна Харриса и Лоентара дает еще один интересный однострочный тест:

delim=$'\x1F' # define a control code to be used as more or less reliable delimiter
if [[ "${delim}${array[@]}${delim}" =~ "${delim}a string to test${delim}" ]]; then
    echo "contains 'a string to test'"
fi

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

0 голосов
/ 10 января 2015

Немного поздно, но вы можете использовать это:

#!/bin/bash
# isPicture.sh

FILE=$1
FNAME=$(basename "$FILE") # Filename, without directory
EXT="${FNAME##*.}" # Extension

FORMATS=(jpeg JPEG jpg JPG png PNG gif GIF svg SVG tiff TIFF)

NOEXT=( ${FORMATS[@]/$EXT} ) # Formats without the extension of the input file

# If it is a valid extension, then it should be removed from ${NOEXT},
#+making the lengths inequal.
if ! [ ${#NOEXT[@]} != ${#FORMATS[@]} ]; then
    echo "The extension '"$EXT"' is not a valid image extension."
    exit
fi
0 голосов
/ 05 июня 2015

Я придумал этот, который работает только в zsh, но я думаю, что общий подход хорош.

arr=( "hello world" "find me" "what?" )
if [[ "${arr[@]/#%find me/}" != "${arr[@]}" ]]; then
    echo "found!"
else
    echo "not found!"
fi

Вы извлекаете свой шаблон из каждого элемента, только если он начинается с ${arr[@]/#pattern/} или заканчивается ${arr[@]/%pattern/} с ним. Эти две замены работают в bash, но оба одновременно ${arr[@]/#%pattern/} работают только в zsh.

Если измененный массив равен оригиналу, то он не содержит элемента.

Edit:

Этот работает в bash:

 function contains () {
        local arr=(${@:2})
        local el=$1
        local marr=(${arr[@]/#$el/})
        [[ "${#arr[@]}" != "${#marr[@]}" ]]
    }

После подстановки сравнивается длина обоих массивов. Очевидно, что если массив содержит элемент, подстановка полностью удалит его, и количество будет отличаться.

0 голосов
/ 07 марта 2016

Вот мой взгляд на эту проблему.Вот короткая версия:

function arrayContains() {
        local haystack=${!1}
        local needle="$2"
        printf "%s\n" ${haystack[@]} | grep -q "^$needle$"
}

И длинная версия, которая, на мой взгляд, намного проще для глаз.

# With added utility function.
function arrayToLines() {
        local array=${!1}
        printf "%s\n" ${array[@]}
}

function arrayContains() {
        local haystack=${!1}
        local needle="$2"
        arrayToLines haystack[@] | grep -q "^$needle$"
}

Примеры:

test_arr=("hello" "world")
arrayContains test_arr[@] hello; # True
arrayContains test_arr[@] world; # True
arrayContains test_arr[@] "hello world"; # False
arrayContains test_arr[@] "hell"; # False
arrayContains test_arr[@] ""; # False
0 голосов
/ 28 февраля 2016

Моя версия техники регулярных выражений, которая уже была предложена:

values=(foo bar)
requestedValue=bar

requestedValue=${requestedValue##[[:space:]]}
requestedValue=${requestedValue%%[[:space:]]}
[[ "${values[@]/#/X-}" =~ "X-${requestedValue}" ]] || echo "Unsupported value"

Здесь происходит то, что вы расширяете весь массив поддерживаемых значений в слова и добавляете конкретную строку, в данном случае «X-», к каждому из них и делаете то же самое для запрошенного значения. Если этот массив действительно содержится в массиве, то результирующая строка будет максимально соответствовать одному из полученных токенов, или, наоборот, ни одному из них. В последнем случае || оператор срабатывает, и вы знаете, что имеете дело с неподдерживаемым значением. До всего этого запрошенное значение удаляется из всех начальных и конечных пробелов посредством стандартных манипуляций со строками оболочки.

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

0 голосов
/ 02 марта 2018

У меня был случай, когда мне нужно было проверить, содержался ли идентификатор в списке идентификаторов, сгенерированных другим скриптом / командой.Для меня сработало следующее:

# the ID I was looking for
ID=1

# somehow generated list of IDs
LIST=$( <some script that generates lines with IDs> )
# list is curiously concatenated with a single space character
LIST=" $LIST "

# grep for exact match, boundaries are marked as space
# would therefore not reliably work for values containing a space
# return the count with "-c"
ISIN=$(echo $LIST | grep -F " $ID " -c)

# do your check (e. g. 0 for nothing found, everything greater than 0 means found)
if [ ISIN -eq 0 ]; then
    echo "not found"
fi
# etc.

Вы также можете сократить / сжать его так:

if [ $(echo " $( <script call> ) " | grep -F " $ID " -c) -eq 0 ]; then
    echo "not found"
fi

В моем случае я запускал jq, чтобы отфильтровать JSON для спискаИдентификаторы и позже пришлось проверить, был ли мой идентификатор в этом списке, и это работало лучше для меня.Он не будет работать для созданных вручную массивов типа LIST=("1" "2" "4"), но с выводом сценария, разделенного символом новой строки.


PS .: не смог прокомментировать ответ, потому что я относительно новый ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...