Как выполнить итерацию по диапазону чисел, определяемых переменными в Bash?

Как перебирать диапазон чисел в Bash, когда диапазон задается переменной?

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

for i in {1..5}; do echo $i; done 

Который дает:

1
2
3
4
5

Тем не менее, как я могу заменить любую из конечных точек диапазона переменной? Это не работает:

 END=5 for i in {1..$END}; do echo $i; done 

Какие принты:

{1..5}

     for i in $(seq 1 $END); do echo $i; done 

    edit: Я предпочитаю seq над другими методами, потому что я действительно могу запомнить это;)

    Метод seq является самым простым, но Bash имеет встроенную арифметическую оценку.

     END=5 for ((i=1;i<=END;i++)); do echo $i done # ==> outputs 1 2 3 4 5 on separate lines 

    for ((expr1;expr2;expr3)); Конструкция работает так же, как и for (expr1;expr2;expr3) на C и подобных языках, и, как и другие ((expr)) случаи, Bash рассматривает их как арифметику.

    обсуждение

    Использование seq прекрасно, как предложил Jiaaro. Pax Diablo предложил цикл Bash, чтобы избежать вызова subprocessа, с дополнительным преимуществом обеспечения большей памяти, если $ END слишком велик. Zathrus обнаружил типичную ошибку в реализации цикла, а также намекнул, что, так как i – текстовая переменная, непрерывные преобразования числа «взад и вперед» выполняются с соответствующим замедлением.

    целочисленная арифметика

    Это улучшенная версия цикла Bash:

     typeset -ii END let END=5 i=1 while ((i<=END)); do echo $i … let i++ done 

    Если единственное, что мы хотим, это echo , тогда мы могли бы написать echo $((i++)) .

    эфемер научил меня чему-то: Bash допускает конструкцию for ((expr;expr;expr)) . Поскольку я никогда не читал всю страницу руководства для Bash (например, я сделал с man-страницей оболочки Korn ( ksh ), и это было давно), я пропустил это.

    Так,

     typeset -ii END # Let's be explicit for ((i=1;i<=END;++i)); do echo $i; done 

    кажется, является наиболее эффективным с точки зрения памяти способом (нет необходимости выделять память, чтобы потреблять выходной сигнал seq , что может быть проблемой, если END очень большой), хотя, вероятно, это не «самый быстрый».

    первоначальный вопрос

    eschercycle отметил, что нотация { a .. b } Bash работает только с литералами; true, соответственно руководству Bash. Можно преодолеть это препятствие с помощью одного (внутреннего) fork() без exec() (как в случае с вызовом seq , для другого изображения требуется fork + exec):

     for i in $(eval echo "{1..$END}"); do 

    И eval и echo являются встроенными Bash, но fork() требуется для подстановки команд (конструкция $(…) ).

    Вот почему оригинальное выражение не работает.

    От человека bash :

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

    Таким образом, расширение расширений – это что-то, сделанное раньше, как чисто текстовая операция макроса, перед расширением параметра.

    Оболочки – это высоко оптимизированные гибриды между макропроцессорами и более формальными языками программирования. Чтобы оптимизировать типичные варианты использования, язык становится более сложным, и некоторые ограничения принимаются.

    Рекомендация

    Я бы предложил придерживаться функций Posix 1 . Это означает использование for i in ; do for i in ; do , если список уже известен, в противном случае используйте while или seq , как в:

     #!/bin/sh limit=4 i=1; while [ $i -le $limit ]; do echo $i i=$(($i + 1)) done # Or ----------------------- for i in $(seq 1 $limit); do echo $i done 

    1. Bash – отличная shell, и я использую ее в интерактивном режиме, но я не ставил bash-isms в мои скрипты. Скриптам может понадобиться более быстрая shell, более безопасная, более встроенная. Им может потребоваться запустить все, что установлено как / bin / sh, и тогда есть все обычные аргументы pro-standards. Помните shellshock, иначе bashdoor?

    Путь POSIX

    Если вы заботитесь о переносимости, используйте пример из стандарта POSIX :

     i=2 end=5 while [ $i -le $end ]; do echo $i i=$(($i+1)) done 

    Вывод:

     2 3 4 5 

    Вещи, которые не являются POSIX:

    • (( )) без доллара, хотя это распространенное расширение, о котором упоминается сам POSIX .
    • [[ . [ здесь достаточно. См. Также: В чем разница между одиночными и двойными квадратными скобками в Bash?
    • for ((;;))
    • seq (GNU Coreutils)
    • {start..end} , и это не может работать с переменными, как указано в руководстве Bash .
    • let i=i+1 : POSIX 7 2. Язык командной оболочки не содержит слова let , и он терпит неудачу в bash --posix 4.3.42
    • может потребоваться доллар в i=$i+1 , но я не уверен. POSIX 7 2.6.4 Арифметическое расширение говорит:

      Если переменная оболочки x содержит значение, которое образует действительную целочисленную константу, необязательно включающую знак «плюс» или «минус», то арифметические разложения «$ ((x))» и «$ (($ x))» должны возвращать то же самое стоимость.

      но, читая его буквально, это не означает, что $((x+1)) расширяется, так как x+1 не является переменной.

    Другой слой косвенности:

     for i in $(eval echo {1..$END}); do ∶ 

    Вы можете использовать

     for i in $(seq $END); do echo $i; done 

    Если вы используете BSD / OS X, вы можете использовать jot вместо seq:

     for i in $(jot $END); do echo $i; done 

    Это прекрасно работает в bash :

     END=5 i=1 ; while [[ $i -le $END ]] ; do echo $i ((i = i + 1)) done 

    Если вам нужен префикс, который вам может понравиться

      for ((i=7;i<=12;i++)); do echo `printf "%2.0d\n" $i |sed "s/ /0/"`;done 

    что даст

     07 08 09 10 11 12 

    Я знаю, что этот вопрос касается bash , но – только для записи – ksh93 умнее и реализует его, как ожидалось:

     $ ksh -c 'i=5; for x in {1..$i}; do echo "$x"; done' 1 2 3 4 5 $ ksh -c 'echo $KSH_VERSION' Version JM 93u+ 2012-02-29 $ bash -c 'i=5; for x in {1..$i}; do echo "$x"; done' {1..5} 

    Это еще один способ:

     end=5 for i in $(bash -c "echo {1..${end}}"); do echo $i; done 

    Все это хорошо, но seq предположительно устарел и больше всего работает с числовыми диапазонами.

    Если вы заключите цикл for в двойных кавычках, стартовые и конечные переменные будут разыменованы, когда вы эхо из строки, и вы можете отправить строку обратно в BASH для выполнения. $i должен быть экранирован, поэтому он НЕ оценивается перед отправкой на подоболочку.

     RANGE_START=a RANGE_END=z echo -e "for i in {$RANGE_START..$RANGE_END}; do echo \\${i}; done" | bash 

    Этот вывод также может быть присвоен переменной:

     VAR=`echo -e "for i in {$RANGE_START..$RANGE_END}; do echo \\${i}; done" | bash` 

    Единственный «служебный» ресурс, который должен генерировать, должен быть вторым экземпляром bash, поэтому он должен быть подходящим для интенсивных операций.

    Замените {} на (( )) :

     tmpstart=0; tmpend=4; for (( i=$tmpstart; i<=$tmpend; i++ )) ; do echo $i ; done 

    Урожайность:

     0 1 2 3 4 

    Если вы выполняете команды оболочки, и у вас (например, я) есть фетиш для конвейерной обработки, это хорошо:

    seq 1 $END | xargs -I {} echo {}

    Если вы хотите оставаться как можно ближе к синтаксису range.bash скобки, попробуйте функцию range из bash-трюков range.bash .

    Например, все следующие будут делать то же самое, что и echo {1..10} :

     source range.bash one=1 ten=10 range {$one..$ten} range $one $ten range {1..$ten} range {1..10} 

    Он пытается поддерживать собственный синтаксис bash с максимально возможным количеством «gotchas»: поддерживаются не только переменные, но часто нежелательное поведение недопустимых диапазонов, передаваемых в виде строк (например, for i in {1..a}; do echo $i; done ) также предотвращается.

    Другие ответы будут работать в большинстве случаев, но все они имеют хотя бы один из следующих недостатков:

    • Многие из них используют подоболочки , что может повредить производительность и может быть невозможно в некоторых системах.
    • Многие из них полагаются на внешние программы. Даже seq – это двоичный файл, который должен быть установлен для использования, должен быть загружен bash и должен содержать ожидаемую программу, так как он будет работать в этом случае. Вездесущий или нет, на это гораздо больше полагаться, чем на сам язык Bash.
    • Решения, которые используют только собственные функции Bash, такие как @ ephemient, не будут работать в алфавитном диапазоне, например {a..z} ; расширение скобки будет. Вопрос был о диапазонах чисел , хотя, так что это каламбур.
    • Большинство из них не являются визуально похожими на синтаксис диапазона расширенного диапазона {1..10} , поэтому программы, которые используют оба, могут быть немного труднее для чтения.
    • Ответ @ bobbogo использует некоторые из привычных синтаксисов, но делает что-то неожиданное, если переменная $END не является допустимым диапазоном «bookend» для другой стороны диапазона. Если END=a , например, ошибка не будет выполнена, и будет {1..a} дословное значение {1..a} . Это поведение по умолчанию Bash, а также – это просто неожиданно.

    Отказ от ответственности: я являюсь автором связанного кода.

    Это работает в Bash и Korn, также может идти от более высоких к более низким числам. Наверное, не самый быстрый или красивый, но работает достаточно хорошо. Обрабатывает и негативы.

     function num_range { # Return a range of whole numbers from beginning value to ending value. # >>> num_range start end # start: Whole number to start with. # end: Whole number to end with. typeset sev s=${1} e=${2} if (( ${e} >= ${s} )); then v=${s} while (( ${v} <= ${e} )); do echo ${v} ((v=v+1)) done elif (( ${e} < ${s} )); then v=${s} while (( ${v} >= ${e} )); do echo ${v} ((v=v-1)) done fi } function test_num_range { num_range 1 3 | egrep "1|2|3" | assert_lc 3 num_range 1 3 | head -1 | assert_eq 1 num_range -1 1 | head -1 | assert_eq "-1" num_range 3 1 | egrep "1|2|3" | assert_lc 3 num_range 3 1 | head -1 | assert_eq 3 num_range 1 -1 | tail -1 | assert_eq "-1" } 
    Давайте будем гением компьютера.