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

Привет всем, информация и советы, которые я здесь прочитал, действительно полезны. Я думаю, что лучше избегать использования seq. Причина в том, что некоторые сценарии должны быть переносимыми и должны работать в самых разных системах unix, где некоторые команды могут отсутствовать. В качестве примера, seq по умолчанию отсутствует в системах FreeBSD.

user557212 14.01.2011 14:19

Я не помню, с какой именно версии Bash, но эта команда также поддерживает конечные нули. Что иногда действительно помогает. Команда for i in {01..10}; do echo $i; done выдаст числа вроде 01, 02, 03, ..., 10.

topr 15.06.2016 12:33

Для тех, кто, как я, просто хочет перебрать диапазон индексов множество, способ bash будет: myarray=('a' 'b' 'c'); for i in ${!myarray[@]}; do echo $i; done (обратите внимание на восклицательный знак). Он более конкретен, чем исходный вопрос, но может помочь. См. расширения параметров bash

PlasmaBinturong 21.02.2018 17:14

Расширение фигурных скобок также используется для таких выражений, как {jpg,png,gif}, которые здесь не рассматриваются напрямую, хотя ответ будет идентичным. См. Раскрытие скобок с переменной? [дубликат], который отмечен как дубликат этого.

tripleee 20.08.2018 08:46

Freebsd, похоже, имеет seq "в наши дни" FWIW ...

rogerdpack 12.03.2021 22:09
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1 764
6
1 306 898
20
Перейти к ответу Данный вопрос помечен как решенный

Ответы 20

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

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

seq включает выполнение внешней команды, которая обычно замедляет работу.

paxdiablo 04.10.2008 05:44

Он не требует выполнения внешней команды для каждой итерации, только один раз. Если время для запуска одной внешней команды является проблемой, вы используете неправильный язык.

Mark Baker 06.10.2008 16:57

Так что это единственный случай, когда это имеет значение - вложение? Мне было интересно, есть ли разница в производительности или какой-то неизвестный технический побочный эффект?

Sqeaky 09.01.2012 23:07

@Squeaky Это отдельный вопрос, ответ на который здесь: stackoverflow.com/questions/4708549/…

tripleee 04.09.2016 22:24

Я включил этот ответ в свой ответ о сравнении производительности ниже. stackoverflow.com/a/54770805/117471 (Это примечание ко мне, чтобы отслеживать, какие из них мне осталось сделать.)

Bruno Bronosky 19.02.2019 19:34
Ответ принят как подходящий

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

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

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

paxdiablo 04.10.2008 05:45

Просто отлично для однострочника. Решение Pax тоже хорошее, но если бы производительность действительно была проблемой, я бы не стал использовать сценарий оболочки.

eschercycle 04.10.2008 05:49

Хорошая точка зрения. Я обнаружил, что создаю быстрый и грязный сценарий ksh, который в конечном итоге превратился в монстра, и я не хотел переписывать его, учитывая время, которое я уже потратил, но это, вероятно, только я.

paxdiablo 04.10.2008 05:51

seq вызывается только один раз для генерации чисел. exec (), это не должно иметь значения, если этот цикл не находится внутри другого жесткого цикла.

Javier 04.10.2008 08:40

Внешняя команда на самом деле неуместна: если вас беспокоят накладные расходы на выполнение внешних команд, вы вообще не хотите использовать сценарии оболочки, но обычно в unix накладные расходы низкие. Однако при высоком уровне END возникает проблема использования памяти.

Mark Baker 06.10.2008 16:53

Обратите внимание, что seq $END будет достаточно, поскольку по умолчанию начинается с 1. Из man seq: «Если FIRST или INCREMENT опущены, по умолчанию используется 1».

fedorqui 'SO stop harming' 05.08.2014 13:06

seq выводит число в формате с плавающей запятой. для большого количества это может сильно раздражать ...

Shai 28.09.2014 16:12

И если вам нужны форматированные числа (например, 001, 002 и т. д.), Вы можете использовать параметр -f для seq: $ (seq -f% 03.0f 1100)

wojtow 27.10.2016 23:54

Имейте в виду, что в bash {4..1} дает 4 3 2 1, поскольку отрицательное «приращение» выводится автоматически. И наоборот, seq 4 1 ничего не дает, потому что приращение по умолчанию - 1. В таких случаях следует использовать полную команду: seq 4 -1 1.

maxime.bochon 07.12.2016 18:33

Как упоминалось в @tzot, это может вызвать проблемы с памятью, если END большой.

Penz 11.01.2017 14:58

СПАСИБО ТЕБЕ ЗА ЭТО!! Изменился ли синтаксис? Я думал, что диапазон определен: 'seq 1 <whatever_number>';

Chris Brocious 26.02.2021 20:28

Это отлично работает в bash:

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

echo $((i++)) работает и объединяет в одну линию.

Bruno Bronosky 30.09.2014 08:19

У этого есть ненужные расширения bash. Версия POSIX: stackoverflow.com/a/31365662/895245

Ciro Santilli TRUMP BAN IS BAD 12.07.2015 10:55

@Ciro, поскольку в вопросе конкретно говорится о bash и есть тег bash, я думаю, вы, вероятно, обнаружите, что `` расширения '' bash более чем в порядке :-)

paxdiablo 12.07.2015 11:29

@paxdiablo Я не имею в виду, что это неправильно, но почему бы не быть переносимым, когда мы можем ;-)

Ciro Santilli TRUMP BAN IS BAD 12.07.2015 11:39

В bash мы можем просто сделать while [[ i++ -le "$END" ]]; do, чтобы выполнить (пост-) приращение в тесте.

Aaron McDaid 12.07.2015 15:49

В bash вы могли бы еще проще сделать while (( i++ < END )); do; почти нет причин использовать [[ ... ]] для арифметических выражений или сравнений.

chepner 16.04.2018 15:53

Я включил этот ответ в свой ответ о сравнении производительности ниже. stackoverflow.com/a/54770805/117471 (Это примечание ко мне, чтобы отслеживать, какие из них мне осталось сделать.)

Bruno Bronosky 19.02.2019 19:33

обсуждение

Как предложил Джиааро, использование 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++)).

эфемерный научил меня кое-чему: 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 отметил, что нотация {а..б} Bash работает только с литералами; true, согласно руководству Bash. Это препятствие можно преодолеть с помощью одного (внутреннего) fork() без exec() (как в случае с вызовом seq, который является другим образом требует fork + exec):

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

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

Единственный недостаток цикла в стиле C в том, что он не может использовать аргументы командной строки, поскольку они начинаются с символа «$».

karatedog 09.01.2012 17:15

@karatedog: for ((i=;i<=;++i)); do echo $i; done в сценарии у меня отлично работает на bash v.4.1.9, поэтому я не вижу проблем с аргументами командной строки. Вы имеете в виду что-то другое?

tzot 09.01.2012 18:41

Кажется, что решение eval быстрее, чем встроенное в C-подобное для: $ time for ((i = 1; i <= 100000; ++ i)); делать :; done real 0m21.220s user 0m19.763s sys 0m1.203s $ time for i in $ (eval echo "{1..100000}"); делать :; Готово; реальный 0m13.881s пользователь 0m13.536s sys 0m0.152s

Marcin Zaluski 04.07.2012 18:55

Да, но eval - это зло ... @MarcinZaluski time for i in $(seq 100000); do :; done намного быстрее!

F. Hauri 02.08.2013 08:58

Производительность должна зависеть от платформы, так как версия eval самая быстрая на моей машине.

Andrew Prock 03.04.2014 03:25

Метод 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 -i i END. Во времена, предшествующие bash (т.е. ksh), это имело значение, но тогда компьютеры были намного медленнее.

tzot 05.10.2008 02:59

Таким образом можно избежать накладных расходов на память, связанных с большим списком, и зависимости от seq. Используй это!

bobbogo 14.03.2011 23:08

Это не будет работать в Ubuntu из-за синтаксической ошибки в вашем коде.

Marin Sagovac 30.11.2014 17:01

@MarinSagovac Этот делает работает, и нет никаких синтаксических ошибок. Вы уверены, что ваша оболочка - Bash?

gniourf_gniourf 13.02.2015 15:05

Расширение bash, отличное от POSIX.

Ciro Santilli TRUMP BAN IS BAD 12.07.2015 10:45

@MarinSagovac Убедитесь, что #!/bin/bash является первой строкой вашего скрипта. wiki.ubuntu.com/…

Melebius 24.04.2017 16:22

просто очень короткий вопрос по этому поводу: почему ((i = 1; i <= END; i ++)) AND NOT ((i = 1; i <= $ END; i ++)); почему нет $ до END?

Baedsch 20.09.2018 13:26

@Baedsch: по той же причине i не используется как $ i. На странице руководства bash для арифметической оценки указано: «В выражении на переменные оболочки также можно ссылаться по имени без использования синтаксиса раскрытия параметров».

user3188140 06.12.2018 14:59

Я включил этот ответ в свой ответ о сравнении производительности ниже. stackoverflow.com/a/54770805/117471 (Это примечание для меня, чтобы отслеживать, какие из них мне осталось сделать.) Я создал этот ответ специально для рассмотрения безоговорочного утверждения @bobbogo и тех, кто его поддержал. СПОЙЛЕР: Накладные расходы на память для большого списка не так страшны, как низкая производительность циклов в стиле c в bash. Прокомментируйте там, если у вас есть мысли. Давайте не будем перехватывать эту ветку.

Bruno Bronosky 19.02.2019 20:05

Еще один уровень косвенности:

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

+1: Также eval 'for i in {1 ..' $ END '}; do ... 'eval кажется естественным способом решить эту проблему.

William Pursell 14.03.2011 23:07

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

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

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

От мужик:

Brace expansion is performed before any other expansions, and any characters special to other expansions are preserved in the result. It is strictly textual. Bash does not apply any syntactic interpretation to the context of the expansion or the text between the braces.

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

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

Recommendation

Я бы посоветовал придерживаться функций 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 is a great shell and I use it interactively, but I don't put bash-isms into my scripts. Scripts might need a faster shell, a more secure one, a more embedded-style one. They might need to run on whatever is installed as /bin/sh, and then there are all the usual pro-standards arguments. Remember shellshock, aka bashdoor?

У меня нет возможностей, но я бы переместил это немного вверх по списку, прежде всего, глядя на пупок bash, но сразу после цикла for в стиле C и арифметической оценки.

mateor 15.03.2013 22:03

Подразумевается, что расширение скобок не экономит много памяти по сравнению с seq для больших диапазонов. Например, echo {1..1000000} | wc показывает, что эхо производит 1 строку, миллион слов и 6 888 896 байт. Попытка seq 1 1000000 | wc дает миллион строк, миллион слов и 6 888 896 байт, а также более чем в семь раз быстрее, по данным команды time.

George 19.01.2015 04:52

Примечание: я уже упоминал метод POSIX while в своем ответе: stackoverflow.com/a/31365662/895245 Но рад, что вы согласны :-)

Ciro Santilli TRUMP BAN IS BAD 04.09.2016 23:09

Я включил этот ответ в свой ответ о сравнении производительности ниже. stackoverflow.com/a/54770805/117471 (Это примечание ко мне, чтобы отслеживать, какие из них мне осталось сделать.)

Bruno Bronosky 19.02.2019 19:34

@mateor Я думал, что цикл C-стиль и арифметическая оценка - это одно и то же решение. Я что-то упускаю?

Oscar Zhang 03.05.2020 18:49

Мне нравится либо цикл for в стиле C, либо while. Одна особенность, отказавшаяся от расширения диапазона bash, - это заполнение нулями с помощью {01..99}. Однако printf (1) может делать то же самое. Вместо echo попробуйте printf "%02i\n" $i в цикле for. Попробуйте printf "%02i\n" $((i++)) для метода цикла while. Строка формата: «0» обозначает площадку с нулем и пробелом, «2» - ширину поля, «i» - целое число, и не забывайте о новой строке :)

JGurtz 09.06.2020 08:34

Все они хороши, но 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, поэтому он должен быть подходящим для интенсивных операций.

Я знаю, что этот вопрос касается 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

Я включил этот ответ в свой ответ о сравнении производительности ниже. stackoverflow.com/a/54770805/117471 (Это примечание ко мне, чтобы отслеживать, какие из них мне осталось сделать.)

Bruno Bronosky 19.02.2019 19:26

Путь POSIX

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

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

Выход:

2
3
4
5

Что такое нет POSIX:

Только что получил 4 положительных голоса за этот ответ, что очень необычно. Если это было размещено на каком-то сайте с агрегацией ссылок, пожалуйста, дайте мне ссылку, ура.

Ciro Santilli TRUMP BAN IS BAD 04.09.2016 22:46

Цитата относится к x, а не ко всему выражению. $((x + 1)) в порядке.

chepner 14.10.2016 15:12

Хотя он не является переносимым и отличается от GNU seq (BSD seq позволяет вам установить строку завершения последовательности с помощью -t), FreeBSD и NetBSD также имеют seq начиная с 9.0 и 3.0, соответственно.

Adrian Günter 23.04.2018 21:31

@CiroSantilli @chepner $((x+1)) и $((x + 1)) анализируют точно так же, так как когда парсер токенизирует x+1, он будет разделен на 3 токена: x, + и 1. x не является допустимым числовым токеном, но является допустимым токеном имени переменной, а x+ - нет, отсюда и разделение. + является допустимым токеном арифметического оператора, а +1 - нет, поэтому токен снова разделяется там. И так далее.

Adrian Günter 23.04.2018 21:37

Я включил этот ответ в свой ответ о сравнении производительности ниже. stackoverflow.com/a/54770805/117471 (Это примечание ко мне, чтобы отслеживать, какие из них мне осталось сделать.)

Bruno Bronosky 19.02.2019 19:34

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

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

Это накладные расходы на порождение другой оболочки.

codeforester 02.01.2017 05:41

На самом деле, это ужасно, потому что порождает 2 снаряда, хотя одного достаточно.

Bruno Bronosky 19.02.2019 19:27

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

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

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

 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/"?

zb226 11.02.2019 14:32

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

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

source range.bash
one=1
ten=10

range {$one..$ten}
range $one $ten
range {1..$ten}
range {1..10}

Он пытается поддерживать собственный синтаксис bash с минимальным количеством ошибок: не только поддерживаются переменные, но также предотвращается часто нежелательное поведение недопустимых диапазонов, передаваемых в виде строк (например, 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}. Это поведение по умолчанию и для 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"
}

Я объединил здесь несколько идей и измерил производительность.

TL; DR Выводы:

  1. seq и {..} действительно быстрые
  2. Шлейфы for и while медленные
  3. $( ) медленный
  4. Петли for (( ; ; )) медленнее
  5. $(( )) еще медленнее
  6. Беспокоиться о числах N в памяти (seq или {..}) глупо (по крайней мере, до 1 миллиона).

Это не выводы. Чтобы сделать выводы, вам нужно будет взглянуть на код C, стоящий за каждым из них. Это больше о том, как мы склонны использовать каждый из этих механизмов для перебора кода. Большинство отдельных операций достаточно близки к одной и той же скорости, что в большинстве случаев не имеет значения. Но такой механизм, как for (( i=1; i<=1000000; i++ )), - это много операций, как вы можете визуально увидеть. Кроме того, операций за петлю намного больше, чем у for i in $(seq 1 1000000). И это может быть неочевидно для вас, поэтому проведение подобных тестов имеет большое значение.

Демо

# show that seq is fast
$ time (seq 1 1000000 | wc)
 1000000 1000000 6888894

real    0m0.227s
user    0m0.239s
sys     0m0.008s

# show that {..} is fast
$ time (echo {1..1000000} | wc)
       1 1000000 6888896

real    0m1.778s
user    0m1.735s
sys     0m0.072s

# Show that for loops (even with a : noop) are slow
$ time (for i in {1..1000000} ; do :; done | wc)
       0       0       0

real    0m3.642s
user    0m3.582s
sys 0m0.057s

# show that echo is slow
$ time (for i in {1..1000000} ; do echo $i; done | wc)
 1000000 1000000 6888896

real    0m7.480s
user    0m6.803s
sys     0m2.580s

$ time (for i in $(seq 1 1000000) ; do echo $i; done | wc)
 1000000 1000000 6888894

real    0m7.029s
user    0m6.335s
sys     0m2.666s

# show that C-style for loops are slower
$ time (for (( i=1; i<=1000000; i++ )) ; do echo $i; done | wc)
 1000000 1000000 6888896

real    0m12.391s
user    0m11.069s
sys     0m3.437s

# show that arithmetic expansion is even slower
$ time (i=1; e=1000000; while [ $i -le $e ]; do echo $i; i=$(($i+1)); done | wc)
 1000000 1000000 6888896

real    0m19.696s
user    0m18.017s
sys     0m3.806s

$ time (i=1; e=1000000; while [ $i -le $e ]; do echo $i; ((i=i+1)); done | wc)
 1000000 1000000 6888896

real    0m18.629s
user    0m16.843s
sys     0m3.936s

$ time (i=1; e=1000000; while [ $i -le $e ]; do echo $((i++)); done | wc)
 1000000 1000000 6888896

real    0m17.012s
user    0m15.319s
sys     0m3.906s

# even a noop is slow
$ time (i=1; e=1000000; while [ $((i++)) -le $e ]; do :; done | wc)
       0       0       0

real    0m12.679s
user    0m11.658s
sys 0m1.004s

Хороший! Однако не согласен с вашим резюме. Мне кажется, что скорость $(seq) примерно такая же, как у {a..b}. Кроме того, каждая операция занимает примерно одинаковое время, поэтому для меня добавляется около 4 мкс на каждую итерацию цикла. Здесь операция - это эхо в теле, арифметическое сравнение, приращение и т. д. Удивительно ли что-нибудь из этого? Кого волнует, сколько времени требуется атрибутам цикла, чтобы выполнить свою работу - время выполнения, вероятно, будет зависеть от содержимого цикла.

bobbogo 20.02.2019 13:46

@bobbogo, вы правы, дело действительно в количестве операций. Я обновил свой ответ, чтобы отразить это. Многие выполняемые нами вызовы на самом деле выполняют больше операций, чем мы могли бы ожидать. Я сузил его из списка примерно из 50 выполненных мною тестов. Я ожидал, что мое исследование будет слишком занудным даже для этой толпы. Как всегда, я предлагаю расставить приоритеты при написании кода следующим образом: сделайте его короче; Сделайте его читабельным; Сделайте это быстрее; Сделайте его портативным. Часто №1 вызывает №3. Не тратьте время на №4, пока это не понадобится.

Bruno Bronosky 20.02.2019 16:54

Это интересное упражнение, хотя начальный вопрос касается использования итерации с переменным счетчиком, что, например, не позволяет {..}.

logicOnAbstractions 13.11.2020 20:10

Есть много способов сделать это, но те, которые я предпочитаю, приведены ниже.

Использование seq

Synopsis from man seq

$ seq [-w] [-f format] [-s string] [-t string] [first [incr]] last

Syntax

Полная команда
seq first incr last

  • первый - это начальный номер в последовательности [необязательно, по умолчанию: 1]
  • incr - приращение [необязательно, по умолчанию: 1]
  • last - это последнее число в последовательности

Пример:

$ seq 1 2 10
1 3 5 7 9

Только с первым и последним:

$ seq 1 5
1 2 3 4 5

Только с последним:

$ seq 5
1 2 3 4 5

Использование {first..last..incr}

Здесь first и last являются обязательными, а incr - необязательными

Использование только первого и последнего

$ echo {1..5}
1 2 3 4 5

Использование incr

$ echo {1..10..2}
1 3 5 7 9

Вы можете использовать это даже для таких персонажей, как показано ниже

$ echo {a..z}
a b c d e f g h i j k l m n o p q r s t u v w x y z

если вы не хотите использовать «seq», «eval» или «jot» или формат арифметического расширения, например. for ((i=1;i<=END;i++)) или другие петли, например. while, и вы не хотите использовать printf и довольны только echo, тогда этот простой обходной путь может соответствовать вашему бюджету:

a=1; b=5; d='for i in {'$a'..'$b'}; do echo -n "$i"; done;' echo "$d" | bash

PS: В моем bash все равно нет команды seq.

Проверено на Mac OSX 10.6.8, Bash 3.2.48

Другие вопросы по теме