Как выполнить итерацию по диапазону чисел, определяемых переменными в 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}
17 ответов
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
прекрасно, как и предположил Цзяаро. 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 научил меня кое-чему: Баш позволяет for ((expr;expr;expr))
конструктов. Поскольку я никогда не читал всю man-страницу для Bash (как я сделал с оболочкой Korn (ksh
) man page, и это было давно), я пропустил это.
и
typeset -i i END # Let's be explicit
for ((i=1;i<=END;++i)); do echo $i; done
кажется, самый эффективный способ памяти (это не нужно будет выделять память для потребления seq
вывод, который может быть проблемой, если конец очень большой), хотя, вероятно, не самый "быстрый".
первый вопрос
eschercycle отметил, что {a..b} нотация Bash работает только с литералами; true, соответственно руководству Bash. Преодолеть это препятствие можно одним (внутренним)fork()
без exec()
(как в случае с вызовом seq
, который будучи другое изображение требует fork + exec):
for i in $(eval echo "{1..$END}"); do
и eval
и echo
являются bash builtins, но a fork()
требуется для замены команды ($(…)
построить).
вот почему исходное выражение не сработало.
С man bash:
расширение расчалки выполнено перед любые другие расширения и любые характеры специальные к другим расширения сохранены в результат. Она строго текстуальна. Удар не применяет синтаксические толкование контекста расширение или текст между фигурные скобки.
и фигурные скобки is что-то сделано раньше как чисто текстовая макрооперация, до расширения параметров.
оболочки - высоко оптимизированные гибриды между макропроцессорами и более формальными языками программирования. Для оптимизации типичных случаев использования язык становится более сложным и принимаются некоторые ограничения.
рекомендация
Я бы предложил придерживаться Posix1 функции. Это означает использование 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. Помни!--7-->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
не является переменной.
Если вы находитесь на BSD / OS X, вы можете использовать jot вместо seq:
for i in $(jot $END); do echo $i; 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}
все это хорошо, но seq предположительно устарел, и большинство работает только с числовыми диапазонами.
Если вы заключаете свой цикл for В двойные кавычки, начальная и конечная переменные будут разыменованы при Эхо-строке, и вы можете отправить строку обратно в BASH для выполнения. $i
должен быть экранирован с \ ' S, поэтому он не оценивается перед отправкой в подсеть.
RANGE_START=a
RANGE_END=z
echo -e "for i in {$RANGE_START..$RANGE_END}; do echo \${i}; done" | bash
этот вывод также может быть назначен a переменная:
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-tricks'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}
brace-расширенный синтаксис диапазона, поэтому программы, которые используют оба могут быть немного труднее читать. - @bobbogo's answer использует некоторые из знакомого синтаксиса, но делает что-то неожиданное, если
$END
переменная не является допустимым диапазоном "bookend" для другой стороны диапазона. ЕслиEND=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 s e v
s=
e=
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"
}