как использовать переменные с расширением фигурной скобки

У меня есть четыре файла:

1.txt  2.txt  3.txt  4.txt

в оболочке Linux я мог бы использовать: ls {1..4}.txt для вывода списка всех четырех файлов, но если я установил две переменные: var1=1 и var2=4, как вывести список из четырех файлов? то есть:

var1=1
var2=4
ls {$var1..$var2}.txt  # error

какой правильный код?


person user2848932    schedule 03.11.2015    source источник
comment
Примечание относительно исходного названия этого вопроса, в котором упоминается подстановка: используемый здесь механизм называется раскрытие скобок; хотя он часто сочетается с подстановкой, это независимый механизм для генерации произвольных строк.   -  person mklement0    schedule 03.11.2015
comment
Оглядываясь назад: этот вопрос почти является дубликатом этого; дополнительным аспектом здесь является желание использовать суффикс (.txt) в расширении.   -  person mklement0    schedule 03.11.2015


Ответы (2)


Использование переменных с формой выражения-последовательности ({<numFrom>..<numTo>}) из расширение скобок работает только в ksh и zsh, но, к сожалению, не работает в bash (и (в основном) только оболочки POSIX-функций, такие как dash, не поддерживают раскрытие скобок вообще, поэтому следует вообще избегать раскрытия скобок с /bin/sh).

Учитывая ваши симптомы, я предполагаю, что вы используете bash, где вы можете использовать только литералы в выражениях последовательности (например, {1..3}); из руководства (выделено мной ):

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

Другими словами: во время вычисления выражения в скобках ссылки на переменные еще не были развернуты (разрешены); Таким образом, интерпретация литералов, таких как $var1 и $var2, как чисел в контексте выражения последовательности невозможна, поэтому выражение в фигурных скобках считается недействительным и не раскрываемым.
Обратите внимание: однако ссылки на переменные расширяются, а именно на более позднем этапе общего расширения; в данном случае литеральным результатом является одно слово '{1..4}' - нераскрытое фигурное выражение с развернутыми значениями переменных.

Несмотря на то, что форма списка раскрытия фигурных скобок (например, {foo,bar)) раскрывается таким же образом, последующее раскрытие переменных не является проблемой, поскольку заранее не требуется интерпретация элементов списка; например {$var1,$var2} правильно приводит к двум словам 1 и 4.
Что касается почему переменные нельзя использовать в выражениях последовательности: исторически, список форма раскрытия скобок была первой, и когда позже была введена форма выражения последовательности, порядок раскрытия уже был фиксированным.
Общий обзор раскрытия фигурных скобок см. в этот ответ.


Обходные пути

Примечание. Обходные пути сосредоточены на выражениях числовой последовательности, как в вопросе; обходной путь на основе eval также демонстрирует использование переменных с менее распространенными выражениями последовательности символов, которые создают диапазоны английских букв (например, {a..c} для получения a b c).


Возможен обходной путь на основе seq, как показано в ответе Джеймсона.

Небольшое предостережение: seq не является утилитой POSIX, но она есть на большинстве современных Unix-подобных платформ.

Чтобы немного улучшить его, используя параметр -f seq для предоставления строки формата в стиле printf и демонстрируя двухзначное заполнение нулями:

seq -f '%02.f.txt' $var1 $var2 | xargs ls # '%02.f'==zero-pad to 2 digits, no decimal places

Обратите внимание: чтобы сделать его полностью надежным — в случае, если результирующие слова содержат пробелы или символы табуляции — вам нужно использовать встроенные кавычки:

seq -f '"%02.f a.txt"' $var1 $var2 | xargs ls 

ls затем видит 01 a.txt, 02 a.txt, ... с правильно сохраненными границами аргументов.

Если вы хотите сначала надежно собрать полученные слова в массив Bash, например, ${words[@]}:

IFS=$'\n' read -d '' -ra words < <(seq -f '%02.f.txt' $var1 $var2)
ls "${words[@]}"

Ниже приведены чистые обходные пути Bash:

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

var1=1 var2=4
# Safety check
(( 10#$var1 + 10#$var2 || 1 )) 2>/dev/null || { echo "Need decimal integers." >&2; exit 1; }
ls $(eval printf '%s\ ' "{$var1..$var2}.txt") # -> ls 1.txt 2.txt 3.txt 4.txt

Аналогичную технику можно применить к выражению последовательности символов;

var1=a var2=c
# Safety check
[[ $var1 == [a-zA-Z] && $var2 == [a-zA-Z] ]] || { echo "Need single letters."; exit 1; }
ls $(eval printf '%s\ ' "{$var1..$var2}.txt") # -> ls a.txt b.txt c.txt

Примечание:

  • Предварительно выполняется проверка, чтобы убедиться, что $var1 и $var2 содержат десятичные целые числа или отдельные английские буквы, что затем делает безопасным использование eval. Как правило, использование eval с непроверенными входными данными представляет собой угрозу безопасности, поэтому лучше избегать использования eval.
  • Учитывая, что выходные данные из eval должны передаваться без кавычек в ls здесь, чтобы оболочка разделяла выходные данные на отдельные аргументы посредством разделения слов, это работает только в том случае, если результирующие имена файлов содержат нет встроенных пробелов или других метасимволов оболочки.

более надежный, но более громоздкий обходной путь для чистого Bash для использования массива для создания эквивалентных слов:

var1=1 var2=4

# Emulate brace sequence expression using an array.
args=()
for (( i = var1; i <= var2; i++ )); do
  args+=( "$i.txt" )
done

ls "${args[@]}"
  • Этот подход не несет угрозы безопасности, а также работает с результирующими именами файлов со встроенными метасимволами оболочки, такими как пробелы.
  • Пользовательские приращения могут быть реализованы путем замены i++, например, i+=2 для шага с шагом 2.
  • Реализация нулевого заполнения потребует использования printf; например, следующим образом:
    args+=( "$(printf '%02d.txt' "$i")" ) # -> '01.txt', '02.txt', ...
person mklement0    schedule 03.11.2015

Для этого конкретного фрагмента синтаксиса («выражения последовательности») вам не повезло, см. Справочная страница Bash:

Выражение последовательности принимает форму {x..y[..incr]}, где x и y — либо целые числа, либо одиночные символы, а incr, необязательное приращение, является целым числом.

Однако вместо этого вы могли бы использовать утилиту seq, которая имела бы аналогичный эффект, и этот подход позволил бы использовать переменные:

var1=1
var2=4
for i in `seq $var1 $var2`; do
    ls ${i}.txt
done

Или, если вас беспокоит вызов ls четыре раза вместо одного, и/или вы хотите, чтобы все это было в одной строке, что-то вроде:

for i in `seq $var1 $var2`; do echo ${i}.txt; done | xargs ls

Со страницы руководства seq(1):

   seq [OPTION]... LAST
   seq [OPTION]... FIRST LAST
   seq [OPTION]... FIRST INCREMENT LAST
person Jameson    schedule 03.11.2015