Переменная для имени массива в фигурных скобках (передача или печать массива в bash)

У меня есть фрагмент кода для распечатки массива в сценарии оболочки:

for i in "${array[@]}"; do
   echo "$i"
   done
}

Я хотел создать из него функцию

printArray() {
    for i in "${$1[@]}"; do
      echo "$i"
      done
}

но когда я вызываю свою функцию с именем массива (которое также доступно в сценарии оболочки), я получаю сообщение об ошибке: ${$1[@]}: неверная замена

Что я обнаружил, так это то, что сначала расширяются фигурные скобки, вероятно, пытаясь буквально расширить «$ 1 [@]».

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

Я ожидаю, что смогу поместить переменную вместо определенного имени массива в свою функцию.

Вы, вероятно, ищете nameref через declare -n

Jetchisel 30.03.2023 10:32

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

Gabriel Staples 30.03.2023 21:03
Структурированный массив Numpy
Структурированный массив Numpy
Однако в реальных проектах я чаще всего имею дело со списками, состоящими из нескольких типов данных. Как мы можем использовать массивы numpy, чтобы...
T - 1Bits: Генерация последовательного массива
T - 1Bits: Генерация последовательного массива
По мере того, как мы пишем все больше кода, мы привыкаем к определенным способам действий. То тут, то там мы находим код, который заставляет нас...
Что такое деструктуризация массива в JavaScript?
Что такое деструктуризация массива в JavaScript?
Деструктуризация позволяет распаковывать значения из массивов и добавлять их в отдельные переменные.
1
2
88
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Вы можете использовать атрибут nameref (представленный в версии 4.3 bash), используя параметр -n для встроенных команд declare или local для выполнения этой задачи:

#!/bin/bash

printArray() {
    if [[ $1 != 'aref' ]]; then
        local -n aref=$1 || return
    fi
    printf '%s\n' "${aref[@]}"
}

echo '*** array=( -n -e -E ); printArray array ***'
array=( -n -e -E ); printArray array

echo '*** aref=( 8 9 10 ); printArray aref ***'
aref=( 8 9 10 ); printArray aref

echo '*** i=( 8 9 10 ); printArray i; declare -p i ***'
i=( 8 9 10 ); printArray i; declare -p i

Найдите nameref в Справочном руководстве по Bash.

Функция printArray может дать сбой по нескольким причинам. Попробуйте: array=( -n -e -E ); printArray array. Попробуйте: aref=( 8 9 10 ); printArray aref. Попробуйте: i=( 8 9 10 ); printArray i; declare -p i.

pjh 30.03.2023 19:28

@pjh Я изменил ответ.

M. Nejat Aydin 30.03.2023 19:55

Все еще есть небольшая проблема, заключающаяся в том, что функция будет печатать пустую строку (вместо ничего), если ей задан пустой массив, но это, вероятно, не имеет значения. Проголосовал.

pjh 30.03.2023 22:02

Я разместил свой код в следующей проверке оболочки: https://www.shellcheck.net/

И это дало мне следующий ответ: https://www.shellcheck.net/wiki/SC2082

# Expand variable names dynamically
var_1 = "hello world"
n=1
name = "var_$n"
echo "${!name}"

Поэтому я сначала расширил имя массива, а затем поставил его с восклицательным знаком в фигурных скобках. После этого ошибка исчезла и массив распечатался.

printArray() {
    array = "$1[@]"
    for i in "${!array}"; do
      echo "$i"
    done
}

Функция printArray может дать сбой по нескольким причинам. Попробуйте: arr=(-n -e -E); printArray arr. Попробуйте: array=(8 9 10); printArray array. Попробуйте: i=(8 9 10); printArray i; declare -p i.

pjh 30.03.2023 19:23
Ответ принят как подходящий

Вот как я передаю и печатаю массивы bash:

Распечатайте массив bash, передав параметры по значению

arrays_join_and_print.sh из моего репозитория eRCaGuy_hello_world:

Обновление: еще лучше, смотрите: array_print.sh сейчас, которое я только что написал. Поиск (импорт)array_print.sh через . array_print.sh дает вам прямой доступ к двум моим функциям печати ниже.

# Function to print all elements of an array.
# Example usage, where `my_array` is a bash array:
#       my_array=()
#       my_array+=("one")
#       my_array+=("two")
#       my_array+=("three")
#       print_array "${my_array[@]}"
print_array() {
    for element in "$@"; do
        printf "    %s\n" "$element"
    done
}

# Usage

# Build an array
array1=()
array1+=("one")
array1+=("two")
array1+=("three")

# Print it
echo "array1:"
print_array "${array1[@]}"

Пример вывода:

array1:
    one
    two
    three

Распечатайте массив bash, передав массив по ссылке

print_array2() {
    # declare a local **reference variable** (hence `-n`) named `array_ref`
    # which is a reference to the first parameter passed in
    local -n array_ref = "$1"

    for element in "${array_ref[@]}"; do
        printf "    %s\n" "$element"
    done
}

# Usage

print_array2 "array1"
# or
print_array2 array1

Выход: такой же, как указано выше.

Идти дальше:

Для передачи массивов в качестве параметров в Bash я представляю 3 решения, которые также можно использовать для печати массивов:

  1. [мой любимый] Передавать массивы по ссылке (обычные массивы и ассоциативные массивы bash)
  2. Вручную сериализовать и десериализовать массивы
  3. Для ассоциативных массивов: передать по ссылке и вручную сериализовать/десериализовать

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

pjh 30.03.2023 22:26

Функция print_array2 завершится ошибкой, если задан массив с именем array_ref или массив с именем element. Также стоит отметить, что он вообще не будет работать с версиями Bash до 4.3 (в которых появились ссылки на имена).

pjh 30.03.2023 22:29

@pjh, интересно. Я знал о проблеме версии Bash для передачи по ссылке, но не о проблеме перекрытия имен переменных.

Gabriel Staples 30.03.2023 22:41

Похоже, решение большинства проблем с bash — «не использовать bash». :) Но я нахожу его гораздо проще и быстрее использовать, чем Python, при объединении команд командной строки, и поэтому я использую Bash гораздо чаще, чем Python.

Gabriel Staples 30.03.2023 22:42

Этот Shellcheck-чистый код должен работать с любой версией Bash:

#! /bin/bash -p

printArray() {
    [[ -z ${1-} || $1 == [[:digit:]]* || $1 == *[^[:alnum:]_]* ]] && return 1
    set -- "$1[@]"
    if [[ -o nounset ]]; then
        set +o nounset
        set -- "${!1}"
        set -o nounset
    else
        set -- "${!1}"
    fi
    (( $# == 0 )) || printf '%s\n' "$@"
}
  • Чтобы избежать конфликтов имен переменных, функция не использует никаких переменных, кроме позиционных параметров ($1, $2, ...).
  • [[ -z ${1-} || $1 == [[:digit:]]* || $1 == *[^[:alnum:]_]* ]] && return 1 гарантирует, что аргумент ($1) является допустимым именем переменной. Это необходимо, потому что некоторые механизмы Bash для доступа к переменным через другие переменные (включая косвенное раскрытие, namerefs и eval) могут привести к внедрению кода, если они используются с недопустимыми именами переменных.
  • set -- "$1[@]" заменяет первый позиционный параметр ($1) (например, arrname) строкой, которую можно использовать с косвенным расширением для расширения до списка элементов в массиве (например, arrname[@]).
  • set -- "${!1}" заменяет позиционные параметры элементами массива.
  • Код, относящийся к nounset, необходим из-за давней ошибки в Bash. Использование set -o nounset (или set -u) в коде Bash заставляет его рассматривать доступ к неустановленным переменным как ошибки. Некоторые люди и команды делают это для всех программ Bash, потому что это может помочь избежать ошибок из-за неинициализированных переменных. Однако эта функция исторически вызывала проблемы для кода, использующего массивы, потому что Bash неправильно обрабатывает пустые массивы как неинициализированные переменные в некоторых контекстах. Большинство проблем было исправлено в Bash 4.4, но я обнаружил, что они сохраняются при непрямых расширениях даже в Bash 5. set -- "${!1}" вызывает ошибку, если массив, на который указывает ссылка, пуст и nounset включен. Код решает проблему, отключая nounset перед выполнением расширения и повторно включая его после этого, если он уже был включен.
  • Наконец, коды используют printf для печати всех позиционных параметров, по одному на строку. Тест (( $# == 0 )) необходим для предотвращения печати пустой строки, если массив пуст (и поэтому нет позиционных параметров).

ПРЕДУПРЕЖДЕНИЕ

Хотя я думаю, что описанная выше функция очень надежна, я не думаю, что она полезна. Вывод функции может не дать точного представления о том, что находится в массиве. Например, он выведет один и тот же вывод для обоих этих массивов: array=(1 2 3) (3 элемента) и array=($'1\n2\n3') (1 элемент).

Bash имеет встроенный механизм однозначной печати содержимого массива: declare -p arrname. Я бы всегда использовал это вместо функции печати массива.

Другие вопросы по теме