bash - заголовок sh файла




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

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

Я знаю, что могу это сделать (это называется «выражение последовательности» в documentation 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}


обсуждение

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

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

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

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

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

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

Так,

typeset -i i 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 <list>; do for i in <list>; 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 - отличная оболочка, и я использую ее в интерактивном режиме, но я не ставил bash-isms в мои скрипты. Скриптам может понадобиться более быстрая оболочка, более безопасная, более встроенная. Им может потребоваться запустить все, что установлено как / bin / sh, и тогда есть все обычные аргументы pro-standards. Помните shellshock, иначе bashdoor?


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

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

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

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

что даст

07
08
09
10
11
12

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

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

Если вы хотите оставаться как можно ближе к синтаксису 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 ) также предотвращается.

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

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

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


Метод 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 рассматривает их как арифметику.


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

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

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

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

Это работает в 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 s e v
   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"
}

Путь 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 $(seq 1 $END); do echo $i; done

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





syntax