Как мне перебрать диапазон чисел, определенных переменными в Bash? - PullRequest
1272 голосов
/ 04 октября 2008

Как перебрать диапазон чисел в 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}

Ответы [ 19 ]

1431 голосов
/ 04 октября 2008
for i in $(seq 1 $END); do echo $i; done

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

367 голосов
/ 05 октября 2008

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

172 голосов
/ 04 октября 2008

обсуждение

Использование seq хорошо, как предложил Джиаро. Pax Diablo предложил цикл Bash, чтобы избежать вызова подпроцесса, с дополнительным преимуществом большей дружественности памяти, если $ END слишком велик. Затрус заметил типичную ошибку в реализации цикла, а также намекнул, что, поскольку 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 (как я делал с справочной страницей оболочки 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 работает только с литералами; правда, в соответствии с руководством Bash. Это препятствие можно преодолеть с помощью одного (внутреннего) fork() без exec() (как в случае с вызовом seq, который для другого изображения требует форка + exec):

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

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

95 голосов
/ 20 апреля 2011

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

От человек Баш :

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

Итак, расширение фигурной скобки - это нечто, что делалось раньше как чисто текстовая макрооперация, до расширения параметра.

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

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

Я бы предложил придерживаться функций Posix 1 . Это означает использование 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, и тогда есть все обычные аргументы, соответствующие стандартам. Помните Shellshock, aka bashdoor?
60 голосов

Путь POSIX

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

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

Выход:

2
3
4
5

Вещи, которые не POSIX:

31 голосов
/ 14 марта 2011

Еще один слой косвенности:

for i in $(eval echo {1..$END}); do
    ∶
23 голосов
/ 04 октября 2008

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

for i in $(seq $END); do echo $i; done
18 голосов
/ 03 июня 2017

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

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

, что даст

07
08
09
10
11
12
18 голосов
/ 16 марта 2011

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

for i in $(jot $END); do echo $i; done
15 голосов
/ 04 октября 2008

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

END=5
i=1 ; while [[ $i -le $END ]] ; do
    echo $i
    ((i = i + 1))
done
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...