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

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

#!/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}

Можно ли генерировать имена var на лету?

 Dennis Williamson30 мая 2012 г., 19:56
Почему вы не используете массив? УвидетьBashFAQ/006.
 pQB31 мая 2012 г., 09:16
@DennisWilliamson Главным образом, потому что это была первая идея, которая пришла мне в голову, и мне нужно было быстро провести тест.

Ответы на вопрос(5)

а просто дополнение к ответу Микеля, которое я не смог уместить в комментарии.

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

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
 16 окт. 2016 г., 20:15
Версия Bash 4.0 очень удобна! Спасибо!

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
 30 мая 2012 г., 21:19
Eval представляет проблемы безопасности, которых не будет ассоциативный массив или косвенная переменная. Увидетьmywiki.wooled,ge.org/BashFAQ/048
The Problem

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

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

Ways to Address the Problem

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

Multi-stage dereferencing of an interpolated variable, via the eval builtin or indirect variable expansion. Iterating over an array, or using i as an index into an array. Dereferencing with 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

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

Dereferencing with Indirect Variables

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

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, но результат во многом такой же.

Iterating Over an Array

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

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

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

You don't need to specify a complex loop test. You access individual array elements via the $SAMPLE[$i] syntax. You can get the total number of elements with the ${#SAMPLE} variable expansion. Practical Equivalency for Original Example

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

 30 мая 2012 г., 23:16
Я удалил свое отрицательное голосование. Я думаю, что с помощьюeval является избыточным, когда доступно косвенное расширение переменной. Однако оба они являются худшими решениями, поскольку они просто имитируют индексацию массива, которая уже доступна.
 30 мая 2012 г., 21:21
Согласитесь с chepner - в противном случае это отличный ответ, но в примере, использующем eval, должно быть предупреждение, чтобы избежать такого использования. Может также стоить добавить пример ассоциативных массивов bash 4, если целью является полнота.
 30 мая 2012 г., 18:56
-1 за использованиеeval где ненужно.
 30 мая 2012 г., 21:55
Пожалуйста, не стесняйтесь указывать, как Eval отличается от косвенных переменныхfor this use case если вы чувствуете это сильно по этому поводу. Однако многие люди избегают eval рефлексивно, даже когда это правильный инструмент для работы, и если вы можете доверять своему источнику ввода, вы можете доверять eval. Добавление заявления об отказе от ответственности в каждое сообщение о потенциальном зле eval похоже на вездесущие ярлыки с надписью «Предупреждение: этот кофе может быть горячим».
 21 нояб. 2016 г., 11:08
из всех 3-х решений eval - единственное, которое работает в золе. +1 за ответ. если бы вы никогда не предполагали использовать команду, ее бы там не было.

Not as far as I know, Они кстати @ johnshen64 сказали. Кроме того, вы можете решить вашу проблему, используя массив следующим образом:

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

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

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

 30 мая 2012 г., 19:55
Или вы могли бы сделать назначение следующим образом (начиная с нуля):SAMPLE=('1-first.with.custom.name' '2-second.with.custom.name') или один на основе:SAMPLE=([1]='1-first.with.custom.name' '2-second.with.custom.name'), Вы можете разбить назначение на несколько строк, если хотите.
 30 мая 2012 г., 18:57
Это не ассоциативный массив, а обычный массив. Ассоциативные массивы используют произвольные строки в качестве индекса.
 30 мая 2012 г., 22:32
@chepner ты прав; фиксированный
 pQB30 мая 2012 г., 18:47
Хороший вопрос, но я уже создал группу SAMPLE # :)
Решение Вопроса

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под «Расширением параметра»:

"If the first character of parameter is an exclamation point (!), a level of variable indirection is introduced. Bash uses the value of the variable formed from the rest of parameter as the name of the variable; this variable is then expanded and that value is used in the rest of the substitution, rather than the value of parameter itself. This is known as indirect expansion."

 30 мая 2012 г., 18:37
что это за оболочка?
 30 мая 2012 г., 18:49
Блестяще, большое спасибо за ссылку. Я бы сказал, что это лучший ответ.
 30 мая 2012 г., 18:40
Это на самом деле работает в Bash, хорошо! @ johnshen64, откуда ты это узнал? Как это называется в справочной странице?
 30 мая 2012 г., 19:02
спасибо Dogbane за подробное объяснение. Действительно полезно знать. Я просто использую это и никогда не удосужился узнать, как это называется :-)
 30 мая 2012 г., 18:47
Взгляните на Расширение параметров на странице руководства Bash. За${parameter}: & quot; Если первым символом параметра является восклицательный знак (!), вводится уровень косвенной косвенности. Bash использует значение переменной, сформированной из остальной части параметра, в качестве имени переменной; эта переменная затем раскрывается, и это значение используется в остальной части замещения, а не в значении самого параметра. Это известно как косвенное расширение. & Quot;

Ваш ответ на вопрос