Как я могу генерировать новые имена переменных "на лету" в оболочке script?

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

#!/bin/bash

SAMPLE1='1-first.with.custom.name'
SAMPLE2='2-second.with.custom.name'

for (( i = 1; i <= 2; i++ ))
do
  echo SAMPLE{$i}
done

Я ожидал бы выход:

1-first.with.custom.name
2-second.with.custom.name

но я получил:

SAMPLE{1}
SAMPLE{2}

Возможно ли генерировать имена переменных на мухе?

Ответ 1

Вам нужно использовать переменное направление:

SAMPLE1='1-first.with.custom.name'
SAMPLE2='2-second.with.custom.name'

for (( i = 1; i <= 2; i++ ))
do
   var="SAMPLE$i"
   echo ${!var}
done

На странице Bash в разделе "Расширение параметров":

"Если первым символом параметра является восклицательный знак (!), вводится уровень переменной косвенности. Bash использует значение переменная, сформированная из остальной части параметра, как имя переменная; эта переменная затем расширяется, и это значение используется в остальное подстановки, а не значение самого параметра. Это называется косвенным расширением.

Ответ 2

Проблема

Вы используете значение i, как если бы это был индекс массива. Это не так, потому что SAMPLE1 и SAMPLE2 являются отдельными переменными, а не массивом.

Кроме того, при вызове echo SAMPLE{$i} вы добавляете значение я в слово "ОБРАЗЕЦ". Единственная переменная, которую вы разыгрываете в этом выражении, - $i, поэтому вы получили результаты, которые вы сделали.

Способы решения проблемы

Существует два основных способа решения этой проблемы:

  • Многоступенчатое разыменование интерполированной переменной с помощью встроенного расширения eval или .
  • Итерирование по массиву или использование я в качестве индекса в массиве.

Разыменование с помощью eval

Самое простое в этой ситуации - использовать eval:

SAMPLE1='1-first.with.custom.name'
SAMPLE2='2-second.with.custom.name'

for (( i = 1; i <= 2; i++ )); do
    eval echo \$SAMPLE${i}
done

Это добавит значение я в конец переменной и затем обработает результирующую строку, расширив имя интерполированной переменной (например, SAMPLE1 или SAMPLE2).

Разыменование с косвенными переменными

Принятый ответ на этот вопрос:

SAMPLE1='1-first.with.custom.name'
SAMPLE2='2-second.with.custom.name'

for (( i = 1; i <= 2; i++ ))
do
   var="SAMPLE$i"
   echo ${!var}
done

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

Итерация по массиву

Вы можете упростить как цикл, так и расширение путем итерации по массиву вместо использования переменной интерполяции. Например:

SAMPLE=('1-first.with.custom.name' '2-second.with.custom.name')
for i in "${SAMPLE[@]}"; do
    echo "$i"
done

Это добавило преимуществ по сравнению с другими методами. В частности:

  • Вам не нужно указывать сложный тест цикла.
  • Доступ к отдельным элементам массива осуществляется с помощью синтаксиса $SAMPLE [$ i].
  • Вы можете получить общее количество элементов с расширением переменной ${# SAMPLE}.

Практическая эквивалентность для оригинального примера

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

Ответ 3

Не насколько я знаю, Они сказали @johnshen64. Кроме того, вы можете решить свою проблему, используя такой массив:

SAMPLE[1]='1-first.with.custom.name'
SAMPLE[2]='2-second.with.custom.name'

for (( i = 1; i <= 2; i++ )) do
    echo ${SAMPLE[$i]}
done

Обратите внимание, что вам не нужно использовать числа, поскольку индексы SAMPLE[hello] будут работать так же хорошо

Ответ 4

Вы можете использовать eval, как показано ниже:

SAMPLE1='1-first.with.custom.name'
SAMPLE2='2-second.with.custom.name'

for (( i = 1; i <= 2; i++ ))
do
  eval echo \$SAMPLE$i
done

Ответ 5

Не автономный ответ, просто дополнение к Miquel, на которое я не мог хорошо поместиться в комментарии.

Вы можете заполнить массив, используя цикл, оператор + = и документ здесь:

SAMPLE=()
while read; do SAMPLE+=("$REPLY"); done <<EOF
1-first.with.custom.name
2-second.with.custom.name
EOF

В bash 4.0 это так же просто, как

readarray SAMPLE <<EOF
1-first.with.custom.name
2-second.with.custom.name
EOF