Как я могу генерировать новые имена переменных "на лету" в оболочке 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