Передача массивов в качестве параметров в bash - PullRequest
177 голосов
/ 30 июня 2009

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

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

Ответы [ 13 ]

206 голосов
/ 25 октября 2010

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

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
83 голосов
/ 30 июня 2009

Примечание: Это довольно грубое решение, которое я отправил сам, не найдя ответа здесь о переполнении стека. Он допускает передачу только одного массива и является последним элементом списка параметров. На самом деле, он вообще не передает массив, а список его элементов, которые повторно собираются в массив с помощью метода named_function (), но это сработало для меня. Несколько позже Кен опубликовал свое решение, но я оставил свое здесь для «исторической» ссылки.

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

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

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

37 голосов
/ 26 июля 2011

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

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

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

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

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

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

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

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

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

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

В таком случае, почему бы не посмотреть непосредственно на эти переменные? Это как писать там:

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

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

В итоге

Это, по сути, ничего не передает по значению - как обычно.

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

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

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

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

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

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

Основная проблема здесь заключается в том, что разработчик (-и) 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)

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

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 голосов
/ 30 июня 2009

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

Более простой подход будет

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

  # now get a copy of the positional parameters
  local_array=("$@")
  ...
}
3 голосов
/ 06 декабря 2012
function aecho {
  set "$1[$2]"
  echo "${!1}"
}

Пример

$ foo=(dog cat bird)

$ aecho foo 1
cat
2 голосов
/ 23 декабря 2016

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

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

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

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

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

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

1 голос
/ 12 июля 2018

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

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's (0 1 2 3 4 5) as expected

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

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

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

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

1 голос
/ 31 июля 2017

Просто для добавления к принятому ответу, поскольку я обнаружил, что он не очень хорошо работает, если содержимое массива примерно такое:

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

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

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

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

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

function RUN_COMMANDS

Только мои 2 ©

1 голос
/ 04 мая 2015

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

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

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\" \"\$@\"; [[ \$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'
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...