Как выполнить итерацию по диапазону чисел в 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 рассматривает их как арифметику.
typeset -ii END
. Во времена до bash (то есть ksh) это имело значение, но компьютеры были намного медленнее.
seq
. Используй это!
Использование 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))
. Поскольку я никогда не читал всю страницу man для Bash (например, я сделал с man-страницей оболочки Korn (ksh
), и это было давно), я пропустил это.
Итак,
typeset -i i END # Let 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()
).
for ((i=$1;i<=$2;++i)); do echo $i; done
в скрипте отлично работает для меня на баш v.4.1.9, так что я не вижу проблемы с аргументами командной строки. Вы имеете в виду что-то еще?
Вот почему исходное выражение не работает.
От человека bash:
Расширение скобки выполняется до любые другие расширения и любые персонажи, специальные для других расширения сохраняются в результат. Это строго текстовое. Bashне применяется никакой синтаксической интерпретации в контексте расширение или текст между фигурные скобки.
Итак, расширение расширений - это нечто, что делается раньше, как чисто текстовая операция макроса, перед расширением параметра.
Корпуса - это высоко оптимизированные гибриды между макропроцессорами и более формальными языками программирования. Чтобы оптимизировать типичные варианты использования, язык становится более сложным, и некоторые ограничения принимаются.
Рекомендация
Я предлагаю придерживаться функций 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
seq
для больших диапазонов. Например, echo {1..1000000} | wc
показывает, что эхо выдает 1 строку, миллион слов и 6 888 896 байт. Try seq 1 1000000 | wc
дает миллион строк, миллион слов и 6888896 байт, а также более чем в семь раз быстрее, чем измеряется командой time
.
Способ 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 0,42может потребоваться доллар в i=$i+1
, но я не уверен. POSIX 7 2.6.4 Арифметическое расширение говорит:
Если переменная оболочки x содержит значение, которое образует действительную целочисленную константу, необязательно включающую знак "плюс" или "минус", то арифметические разложения "$ ((x))" и "$ ((x))" должны вернуть то же значение.
но в буквальном смысле это означает, что не означает, что $((x+1))
расширяется, поскольку x+1
не является переменной.
x
, а не ко всему выражению. $((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
((i++))
работает
echo $((i++))
работает и объединяет его в одну строку.
Если вам нужен префикс, который вам может понравиться
for ((i=7;i<=12;i++)); do echo `printf "%2.0d\n" $i |sed "s/ /0/"`;done
что даст
07
08
09
10
11
12
printf "%02d\n" $i
проще, чем printf "%2.0d\n" $i |sed "s/ /0/"
?
Это другой способ:
end=5
for i in $(bash -c "echo {1..${end}}"); do echo $i; done
Я знаю, что этот вопрос касается 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}
Замените {}
на (( ))
:
tmpstart=0;
tmpend=4;
for (( i=$tmpstart; i<=$tmpend; i++ )) ; do
echo $i ;
done
Урожайность:
0
1
2
3
4
Все они хороши, но 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, поэтому он должен быть подходящим для интенсивных операций.
Если вы выполняете команды оболочки, и у вас (как у I) есть фетиш для конвейерной обработки, это хорошо:
seq 1 $END | xargs -I {} echo {}
Если вы хотите как можно ближе подойти к синтаксису brace-expression, попробуйте 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.{a..z}
; расширение скобки будет. Вопрос был о диапазонах чисел, однако, так что это каламбур.{1..10}
с расширенным диапазоном, поэтому программы, которые используют оба, могут быть немного труднее читать.$END
не является допустимым диапазоном "bookend" для другой стороны диапазона. Если END=a
, например, ошибка не будет выполнена, а значение verbatim {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=${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"
}