Передача массивов в качестве параметров в bash - программирование

Передача массивов в качестве параметров в bash

Как передать массив как параметр функции bash?

Примечание.. После того, как вы не нашли ответ здесь в разделе "Переполнение стека", я опубликовал свое несколько грубое решение. Он позволяет передавать только один массив и быть последним элементом списка параметров. Фактически, это не передача массива вообще, а список его элементов, которые повторно собраны в массив by_function(), но это сработало для меня. Если кто-то знает лучший способ, не стесняйтесь добавлять его здесь.

4b9b3361

Ответ 1

Вы можете передать несколько массивов в качестве аргументов, используя что-то вроде этого:

takes_ary_as_arg()
{
    declare -a argAry1=("${!1}")
    echo "${argAry1[@]}"

    declare -a argAry2=("${!2}")
    echo "${argAry2[@]}"
}
try_with_local_arys()
{
    # array variables could have local scope
    local descTable=(
        "sli4-iread"
        "sli4-iwrite"
        "sli3-iread"
        "sli3-iwrite"
    )
    local optsTable=(
        "--msix  --iread"
        "--msix  --iwrite"
        "--msi   --iread"
        "--msi   --iwrite"
    )
    takes_ary_as_arg descTable[@] optsTable[@]
}
try_with_local_arys

будет эхо:

sli4-iread sli4-iwrite sli3-iread sli3-iwrite  
--msix  --iread --msix  --iwrite --msi   --iread --msi   --iwrite

Ответ 2

Примечание.. Это несколько грубое решение, которое я разместил сам, после того, как не нашел ответа здесь на Stack Overflow. Он позволяет передавать только один массив и быть последним элементом списка параметров. Фактически, это не передача массива вообще, а список его элементов, которые повторно собраны в массив by_function(), но это сработало для меня. Несколько позже Кен опубликовал его решение, но я сохранил свое место для "исторической" ссылки.

calling_function()
{
    variable="a"
    array=( "x", "y", "z" )
    called_function "${variable}" "${array[@]}"
}

called_function()
{
    local_variable="${1}"
    shift
    local_array=("${@}")
}

Улучшено TheBonsai, спасибо.

Ответ 3

Комментируя решение Кена Бертельсона и отвечая Ян Хеттиху:

Как это работает

строка takes_ary_as_arg descTable[@] optsTable[@] в функции try_with_local_arys() отправляет:

  • На самом деле создается копия массивов descTable и optsTable, доступных для функции takes_ary_as_arg.
  • takes_ary_as_arg() функция получает descTable[@] и optsTable[@] как строки, это означает $1 == descTable[@] и $2 == optsTable[@].
  • в начале функции takes_ary_as_arg() он использует синтаксис ${!parameter}, который называется косвенным ссылкой или иногда двойным ссылкой, это означает, что вместо значения $1 мы используем значение расширенного значения $1, например:

    baba=booba
    variable=baba
    echo ${variable} # baba
    echo ${!variable} # booba
    

    аналогично для $2.

  • Поместите это в argAry1=("${!1}"), создайте argAry1 в виде массива (скобки, следующие за =), с расширенным descTable[@], так же, как прямое письмо argAry1=("${descTable[@]}"). declare не требуется.

N.B.: Стоит отметить, что инициализация массива с использованием этой формы скобок инициализирует новый массив в соответствии с IFS или внутренним разделителем полей, который по умолчанию включает вкладку, новую строку и пробел. в этом случае, поскольку он использовал обозначение [@], каждый элемент рассматривается сам по себе, как если бы он цитировался (в отличие от [*]).

Мое бронирование с ним

В BASH локальная переменная scope - это текущая функция и каждая дочерняя функция, вызываемая из нее, это означает, что takes_ary_as_arg() функция "видит" те массивы descTable[@] и optsTable[@], поэтому она работает (см. выше объяснение).

В этом случае, почему бы прямо не посмотреть на эти переменные сами? Это похоже на письмо:

argAry1=("${descTable[@]}")

См. выше объяснение, которое просто копирует значения массива descTable[@] в соответствии с текущим IFS.

В заключение

Это, по сути, не имеет значения - как обычно.

Я также хочу подчеркнуть комментарий Денниса Уильямсона выше: редкие массивы (массивы без всех ключей определяют - с "дырами" в них) не будут работать так, как ожидалось, - мы потеряем ключи и "конденсируем" массив.

При этом я вижу значение для обобщения, поэтому функции могут получать массивы (или копии), не зная имена:

  • для ~ "копий": эта техника достаточно хороша, просто нужно держать в курсе, что индексы (ключи) исчезли.
  • для реальных копий: мы можем использовать eval для ключей, например:

    eval local keys=(\${!$1})
    

а затем цикл, использующий их для создания копии. Примечание: здесь ! не используется предыдущая косвенная/двойная оценка, а скорее в контексте массива он возвращает индексы массива (ключи).

  • и, конечно, если мы должны были передать строки descTable и optsTable (без [@]), мы могли бы использовать сам массив (как в ссылке) с помощью eval. для общей функции, которая принимает массивы.

Ответ 4

Основная проблема заключается в том, что разработчик bash, который проектировал/реализовал массивы, действительно ввернул пучок. Они решили, что ${array} была просто короткой рукой для ${array[0]}, что было плохой ошибкой. Особенно, если вы считаете, что ${array[0]} не имеет смысла и оценивает пустую строку, если тип массива ассоциативен.

Назначение массива принимает форму array=(value1 ... valueN), где значение имеет синтаксис [subscript]=string, тем самым присваивая значение непосредственно определенному индексу в массиве. Это делает так, что могут быть два типа массивов, индексированные по индексам и индексы хешей (называемые ассоциативными массивами на языке bash). Это также делает так, что вы можете создавать разреженные массивы с числовой индексацией. Оставшаяся часть [subscript]= является короткой рукой для массива с числовой индексацией, начиная с порядкового индекса 0 и увеличивая с каждым новым значением в инструкции присваивания.

Следовательно, ${array} должен оценивать весь массив, индексы и все. Он должен оценивать обратный оператор присваивания. Любой майор CS третьего года должен это знать. В этом случае этот код будет работать точно так, как вы могли бы ожидать:

declare -A foo bar
foo=${bar}

Затем передача массивов по значению в функции и назначение одного массива другому будет работать, как диктует остальная часть синтаксиса оболочки. Но поскольку они не сделали этого правильно, оператор присваивания = не работает для массивов, а массивы не могут передаваться по значению функциям или подселам или выводам вообще (echo ${array}) без кода для жевания через все это.

Итак, если бы это было сделано правильно, то следующий пример продемонстрировал бы, насколько полезность массивов в bash может быть значительно лучше:

simple=(first=one second=2 third=3)
echo ${simple}

результирующий вывод должен быть:

(first=one second=2 third=3)

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

declare -A foo
read foo <file

Увы, мы были разочарованы превосходной командой разработчиков bash.

Таким образом, чтобы передать массив функции, существует только один вариант, и это должно использовать функцию nameref:

function funky() {
    local -n ARR

    ARR=$1
    echo "indexes: ${!ARR[@]}"
    echo "values: ${ARR[@]}"
}

declare -A HASH

HASH=([foo]=bar [zoom]=fast)
funky HASH # notice that I'm just passing the word 'HASH' to the function

приведет к следующему выводу:

indexes: foo zoom
values: bar fast

Так как это передача по ссылке, вы также можете назначить массив в функции. Да, массив, на который делается ссылка, должен иметь глобальную область видимости, но это не должно быть слишком большой сделкой, учитывая, что это сценарий оболочки. Чтобы передать ассоциативный или разреженный индексированный массив по значению функции, необходимо бросить все индексы и значения в список аргументов (не слишком полезно, если это большой массив), как одиночные строки, такие как:

funky "${!array[*]}" "${array[*]}"

а затем записывая кучу кода внутри функции, чтобы собрать массив.

Ответ 5

Ответ DevSolar имеет одну точку, которую я не понимаю (может быть, у него есть конкретная причина сделать это, но я не могу думать об этом): он устанавливает массив из элемента позиционных параметров по элементу, итеративный.

Более простой способ был бы

called_function()
{
  ...
  # do everything like shown by DevSolar
  ...

  # now get a copy of the positional parameters
  local_array=("[email protected]")
  ...
}

Ответ 6

function aecho {
  set "$1[$2]"
  echo "${!1}"
}

Пример

$ foo=(dog cat bird)

$ aecho foo 1
cat

Ответ 7

Простым способом передачи нескольких массивов в качестве параметра является использование строки, разделенной символами. Вы можете вызвать свой script следующим образом:

./myScript.sh "value1;value2;value3" "somethingElse" "value4;value5" "anotherOne"

Затем вы можете извлечь его в свой код следующим образом:

myArray=$1
IFS=';' read -a myArray <<< "$myArray"

myOtherArray=$3
IFS=';' read -a myOtherArray <<< "$myOtherArray"

Таким образом, вы можете передавать несколько массивов в качестве параметров и не обязательно должны быть последними параметрами.

Ответ 8

Это работает даже с пробелами:

format="\t%2s - %s\n"

function doAction
{
  local_array=("[email protected]")
  for (( i = 0 ; i < ${#local_array[@]} ; i++ ))
    do
      printf "${format}" $i "${local_array[$i]}"
  done
  echo -n "Choose: "
  option=""
  read -n1 option
  echo ${local_array[option]}
  return
}

#the call:
doAction "${tools[@]}"

Ответ 9

С помощью нескольких трюков вы можете передавать именованные параметры в функции вместе с массивами.

Метод, который я разработал, позволяет вам обращаться к параметрам, переданным такой функции:

testPassingParams() {

    @var hello
    l=4 @array anArrayWithFourElements
    l=2 @array anotherArrayWithTwo
    @var anotherSingle
    @reference table   # references only work in bash >=4.3
    @params anArrayOfVariedSize

    test "$hello" = "$1" && echo correct
    #
    test "${anArrayWithFourElements[0]}" = "$2" && echo correct
    test "${anArrayWithFourElements[1]}" = "$3" && echo correct
    test "${anArrayWithFourElements[2]}" = "$4" && echo correct
    # etc...
    #
    test "${anotherArrayWithTwo[0]}" = "$6" && echo correct
    test "${anotherArrayWithTwo[1]}" = "$7" && echo correct
    #
    test "$anotherSingle" = "$8" && echo correct
    #
    test "${table[test]}" = "works"
    table[inside]="adding a new value"
    #
    # I'm using * just in this example:
    test "${anArrayOfVariedSize[*]}" = "${*:10}" && echo correct
}

fourElements=( a1 a2 "a3 with spaces" a4 )
twoElements=( b1 b2 )
declare -A assocArray
assocArray[test]="works"

testPassingParams "first" "${fourElements[@]}" "${twoElements[@]}" "single with spaces" assocArray "and more... " "even more..."

test "${assocArray[inside]}" = "adding a new value"
Другими словами, вы можете не только вызывать свои параметры по их именам (что составляет более читаемое ядро), вы можете фактически передавать массивы (и ссылки на переменные - эта функция работает только в bash 4.3)! Кроме того, отображаемые переменные находятся в локальной области, как и $1 (и другие).

Код, который делает эту работу, довольно светлый и работает как в bash 3, так и в bash 4 (это единственные версии, с которыми я его тестировал). Если вам интересно больше таких трюков, которые делают разработку с bash намного приятнее и проще, вы можете взглянуть на мою Bash Infinity Framework, для этой цели был разработан код ниже.

Function.AssignParamLocally() {
    local commandWithArgs=( $1 )
    local command="${commandWithArgs[0]}"

    shift

    if [[ "$command" == "trap" || "$command" == "l="* || "$command" == "_type="* ]]
    then
        paramNo+=-1
        return 0
    fi

    if [[ "$command" != "local" ]]
    then
        assignNormalCodeStarted=true
    fi

    local varDeclaration="${commandWithArgs[1]}"
    if [[ $varDeclaration == '-n' ]]
    then
        varDeclaration="${commandWithArgs[2]}"
    fi
    local varName="${varDeclaration%%=*}"

    # var value is only important if making an object later on from it
    local varValue="${varDeclaration#*=}"

    if [[ ! -z $assignVarType ]]
    then
        local previousParamNo=$(expr $paramNo - 1)

        if [[ "$assignVarType" == "array" ]]
        then
            # passing array:
            execute="$assignVarName=( \"\${@:$previousParamNo:$assignArrLength}\" )"
            eval "$execute"
            paramNo+=$(expr $assignArrLength - 1)

            unset assignArrLength
        elif [[ "$assignVarType" == "params" ]]
        then
            execute="$assignVarName=( \"\${@:$previousParamNo}\" )"
            eval "$execute"
        elif [[ "$assignVarType" == "reference" ]]
        then
            execute="$assignVarName=\"\$$previousParamNo\""
            eval "$execute"
        elif [[ ! -z "${!previousParamNo}" ]]
        then
            execute="$assignVarName=\"\$$previousParamNo\""
            eval "$execute"
        fi
    fi

    assignVarType="$__capture_type"
    assignVarName="$varName"
    assignArrLength="$__capture_arrLength"
}

Function.CaptureParams() {
    __capture_type="$_type"
    __capture_arrLength="$l"
}

alias @trapAssign='Function.CaptureParams; trap "declare -i \"paramNo+=1\"; Function.AssignParamLocally \"\$BASH_COMMAND\" \"\[email protected]\"; [[ \$assignNormalCodeStarted = true ]] && trap - DEBUG && unset assignVarType && unset assignVarName && unset assignNormalCodeStarted && unset paramNo" DEBUG; '
alias @param='@trapAssign local'
alias @reference='_type=reference @trapAssign local -n'
alias @var='_type=var @param'
alias @params='_type=params @param'
alias @array='_type=array @param'

Ответ 10

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

RUN_COMMANDS=(
  "command1 param1... paramN"
  "command2 param1... paramN"
)

В этом случае каждый член массива получает split, поэтому массив, который видит функция, эквивалентен:

RUN_COMMANDS=(
    "command1"
    "param1"
     ...
    "command2"
    ...
)

Чтобы этот случай работал, способ, которым я нашел, - передать имя переменной функции, затем использовать eval:

function () {
    eval 'COMMANDS=( "${'"$1"'[@]}" )'
    for COMMAND in "${COMMANDS[@]}"; do
        echo $COMMAND
    done
}

function RUN_COMMANDS

Просто мое 2 ©

Ответ 11

Как бы ни было упущено, вот обходной путь, который работает до тех пор, пока вы не передаете массив явно, а переменную, соответствующую массиву:

function passarray()
{
    eval array_internally=("$(echo '${'$1'[@]}')")
    # access array now via array_internally
    echo "${array_internally[@]}"
    #...
}

array=(0 1 2 3 4 5)
passarray array # echo (0 1 2 3 4 5) as expected

Я уверен, что кто-то может придумать ясную реализацию идеи, но я нашел, что это лучшее решение, чем передача массива как "{array[@]"} а затем доступ к нему внутренне с помощью array_inside=("[email protected]"). Это становится сложным, когда есть другие параметры positional/getopts. В этих случаях я должен был сначала определить, а затем удалить параметры, не связанные с массивом, используя некоторую комбинацию удаления элемента shift и array.

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

eval $target_varname=$"(${array_inside[@]})"

Надеюсь, это поможет кому-то.

Ответ 12

Требование: функция для поиска строки в массиве.
Это небольшое упрощение решения DevSolar, поскольку оно использует переданные аргументы, а не их копирование.

myarray=('foobar' 'foxbat')

function isInArray() {
  local item=$1
  shift
  for one in [email protected]; do
    if [ $one = $item ]; then
      return 0   # found
    fi
  done
  return 1       # not found
}

var='foobar'
if isInArray $var ${myarray[@]}; then
  echo "$var found in array"
else
  echo "$var not found in array"
fi