Вопрос по arrays, bash – Передача массивов в качестве параметров в bash

176

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

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

Here у вас есть хорошая ссылка и множество примеров. Artem Barger
Э-э-э ... Три отрицательных голоса по пятилетнему вопросу в одну минуту? DevSolar

Ваш Ответ

13   ответов
19

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

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

Следовательно,${array} следует оценить доentire массив, индексы и все. Следует оценить обратное значение оператора присваивания. Любой третий год 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[*]}"

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

Решение проблемы использованияlocal -n лучше и актуальнее, чем принятый ответ. Это решение также будет работать для переменной любого типа. Пример, приведенный в этом ответе, можно сократить доlocal -n ARR=${1}, Тем не менее-n вариант дляlocal/declare доступно только в Bash версии 4.3 и выше.
Это мило! Небольшая ошибка: если вы передаете переменную с тем же именем, что и локальный аргумент вашей функции (например,funky ARR), оболочка выдаст предупреждениеcircular name referenceпотому что в основном функция попытается сделатьlocal -n ARR=ARR, Хорошоdiscussion об этой теме.
1

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[@]}"
Интересно, какой смысл здесь. Это просто нормальная передача аргументов. & Quot; $ @ & quot; синтаксис работает для пробелов: & quot; $ @ & quot; эквивалентно «$ 1»; & Quot; $ 2 & Quot; ...
Могу ли я передать 2 массива в функцию?
3
function aecho {
  set "$1[$2]"
  echo "${!1}"
}

пример

$ foo=(dog cat bird)

$ aecho foo 1
cat
1

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

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 & # xA9;

2

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

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

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

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

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

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

83

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

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

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

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

Ну, вы можете передать несколько массивов, если вы включите количество элементов каждого массива.called_function "${#array[@]}" "${array[@]}" "${#array2[@]}" "${array2[@]}" и т.д. ... все еще с некоторыми очевидными ограничениями, но на самом деле лучше решить проблему так, как поддерживает язык, а не пытаться заставить язык работать так, как вы привыкли в других языках.
@geirha: Ну, я думаю, нам придется согласиться с тем, что мы не согласны, и вы должны позволить мне быть судьей, ответ на который лучше всего отвечает на мой вопрос. Лично я предпочитаю передавать массивы по ссылкеanyway (независимо от языка, чтобы сохранить копирование данных); тем более, когда альтернативой является изгиб назад и передача размера массива в качестве дополнительного параметра ... DevSolar
@geirha: я бы попросил вас проверить, кто опубликовал вопрос, кто опубликовал этот ответ и кто, вероятно,accepted ответ, который вы называете "плохим". ;-) Вы также можете проверитьNote в вопросе, который указывает, почему это решение уступает Кенсу. DevSolar
Я знаю, что вы задали вопрос, написали этот ответ и приняли неправильный ответ. Вот почему я так сформулировал это. Причина, по которой принятый ответ плохой, заключается в том, что он пытается передать массив по ссылке, чего вам действительно следует избегать. Кроме того, пример объединяет несколько аргументов в одну строку. Если вам действительно нужно передавать массивы по ссылке, то для начала bash - неправильный язык. Даже с новыми переменными nameref в bash 4.3 вы не можете безопасно избежать конфликтов имен (циклическая ссылка).
Через три года после этого, этот ответ, сохраненный только по историческим причинам, получил два отрицательных отзыва в течение нескольких дней. Как обычно печально на SO, без каких-либо замечаний относительно того, почему люди думают, что это оправдано. Обратите внимание, что этот ответpredates все остальные, и что я принял ответ Кена как лучшее решение. Я прекрасно понимаю, что это далеко не идеально, но в течение четырехmonths это был лучший доступный на SO. Почему это должно быть понижено дваyears после того, как он занял второе место, идеальное решение Кена уже не для меня. DevSolar
204

Вы можете пройтиmultiple arrays as arguments используя что-то вроде этого:

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

will echo:

sli4-iread sli4-iwrite sli3-iread sli3-iwrite  
--msix  --iread --msix  --iwrite --msi   --iread --msi   --iwrite
Это замечательно, но может ли Кен или кто-то объяснить пару вещей, которые меня озадачивают, почему это работает: 1 - я бы подумал, что descTable и optsTable должны были бы иметь префикс с $ при передаче в качестве аргументов функции. 2 - В первой строке «берет ...» зачем нужна явная декларация массива? 3 - А что значит! означает в выражении $ {! 1}, и почему [@] там не требуется или даже не разрешен? - Это работает, и все эти детали, кажется, необходимы на основании моих испытаний, но я хотел бы понять, почему!
Следует отметить, что если исходный массив является разреженным, массив в принимающей функции не будет иметь одинаковых индексов.
1: descTable и optsTable просто передаются как имена, таким образом, $ не существует, они должны раскрываться только в вызываемой функции 2: не совсем уверен, но я думаю, что это действительно не нужно 3: the! используется, потому что параметры, передаваемые в функцию, должны быть расширены дважды: $ 1 расширяется до «descTable [@]», и это должно быть расширено до «$ {descTable [@]}». Синтаксис $ {! 1} делает именно это.
Я не думаю, что "объявить -a" часть необходима. Наличие круглых скобок уже определяет LHS присваивания в виде массива.
Этот ответ помог мне решить проблему только сейчас. Однако я хотел бы отметить, что на моей машине (с использованием bash 4.3.42) & quot; $ {! 1} & quot; и & quot; $ {! 2} & quot; нужно удалить цитаты. Если вы этого не сделаете, значение исходного массива читается как одна строка и присваивается argAry1 [0] и argAry2 [0] соответственно, в основном это означает, что структура массива потеряна.
5

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

Проще будет

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

  # now get a copy of the positional parameters
  local_array=("[email protected]")
  ...
}
Моя причина не делать этого состоит в том, что я вообще не играл с массивами bash до нескольких дней назад. Раньше я переходил на Perl, если он стал сложным, - вариант, которого у меня нет на моей нынешней работе. Спасибо за подсказку! DevSolar
35

How it works

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

This is actually creates a copy of the descTable and optsTable arrays which are accessible to the takes_ary_as_arg function. takes_ary_as_arg() function receives descTable[@] and optsTable[@] as strings, that means $1 == descTable[@] and $2 == optsTable[@].

in the beginning of takes_ary_as_arg() function it uses ${!parameter} syntax, which is called indirect reference or sometimes double referenced, this means that instead of using $1's value, we use the value of the expanded value of $1, example:

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

likewise for $2.

putting this in argAry1=("${!1}") creates argAry1 as an array (the brackets following =) with the expanded descTable[@], just like writing there argAry1=("${descTable[@]}") directly. the declare there is not required.

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

My reservation with it

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

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

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

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

In summary

This is passing, in essence, nothing by value - as usual.

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

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

for ~"copies": this technique is good enough, just need to keep aware, that the indices (keys) are gone.

for real copies: we can use an eval for the keys, for example:

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

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

and, of course, if we were to pass descTable and optsTable strings (without [@]), we could use the array itself (as in by reference) with eval. for a generic function that accepts arrays.
Error: User Rate Limit Exceeded
Error: User Rate Limit ExceededArray1Error: User Rate Limit ExceededArray2Error: User Rate Limit Exceeded
1

функции вместе с массивами.

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

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'
0

Requirement: Функция для поиска строки в массиве.
что оно использует переданные аргументы, а не копирует их.

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 
0

function display_two_array {
    local arr1=$1
    local arr2=$2
    for i in $arr1
    do
       "arrary1: $i"
    done
    
    for i in $arr2
    do
       "arrary2: $i"
    done
}

test_array=(1 2 3 4 5)
test_array2=(7 8 9 10 11)

display_two_array "${test_array[*]}" "${test_array2[*]}"
It should be noticed that the ${test_array[*]} а также${test_array2[*]} должен быть окружен "& quot;", иначе вы "потерпите неудачу".

1

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

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=("[email protected]"), Это становится сложным, когда есть другие позиционные /getopts параметры. В этих случаях мне пришлось сначала определить, а затем удалить параметры, не связанные с массивом, с использованием некоторой комбинацииshift и удаление элементов массива.

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

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

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

Похожие вопросы