Як я повторюю діапазон чисел, визначених змінними в Bash?


1542

Як я повторюю діапазон чисел у 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}


26
Привіт усім, інформація та підказки, які я прочитав тут, справді корисні. Я думаю, що найкраще уникати використання seq. Причина полягає в тому, що деякі сценарії повинні бути портативними і повинні працювати в різних системах Unix, де деякі команди можуть бути відсутніми. Для прикладу, seq за замовчуванням не присутній у системах FreeBSD.


9
Я не пам’ятаю, з якої версії Bash точно, але ця команда також підтримує задні нулі. Що іноді дуже корисно. Команда for i in {01..10}; do echo $i; doneдала б цифри на кшталт 01, 02, 03, ..., 10.
topr

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

1
Розширення дужок також використовується для таких виразів, на {jpg,png,gif}які тут не йдеться безпосередньо, хоча відповідь буде ідентичною. Дивіться розширення Brace зі змінною? [дублікат], який позначений як дублікат цього.
трійчатка

Відповіді:


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

редагувати: я віддаю перевагу seqіншим методам, тому що фактично це можу запам'ятати;)


36
seq передбачає виконання зовнішньої команди, яка зазвичай уповільнює справи. Це може не мати значення, але це стає важливим, якщо ви пишете сценарій для обробки великої кількості даних.
paxdiablo

37
Просто чудово для однолінійного. Рішення Pax теж добре, але якби продуктивність справді була проблемою, я б не використовував сценарій оболонки.
eschercycle

17
seq викликається лише один раз для генерації чисел. exec (), це не повинно бути суттєвим, якщо ця петля не знаходиться в іншому щільному циклі.
Хав'єр

29
Зовнішня команда насправді не викликає труднощів: якщо ви турбуєтесь про накладні витрати зовнішніх команд, ви взагалі не хочете використовувати сценарії оболонки, але, як правило, на unix, накладні витрати низькі. Однак існує проблема використання пам'яті, якщо END високо.
Марк Бейкер

18
Зверніть увагу, що seq $ENDбуде достатньо, оскільки за замовчуванням слід починати з 1. З man seq: "Якщо ПЕРШИЙ або ВПРАВЛЕННЯ пропущено, він за замовчуванням дорівнює 1".
fedorqui 'ТАК перестаньте шкодити'

469

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)в С і інших подібних мовах, як і інші ((expr))випадки, Bash розглядає їх як арифметика.


67
Таким чином можна уникнути накладного обміну пам’яті великого списку та залежності від seq. Використай це!
bobbogo

3
@MarinSagovac Це робить роботу , і немає ніяких синтаксичних помилок. Ви впевнені, що ваша оболонка - Bash?
gniourf_gniourf

3
@MarinSagovac Не забудьте зробити #!/bin/bashперший рядок свого сценарію. wiki.ubuntu.com/…
Мелебій

6
лише дуже коротке запитання щодо цього: чому ((i = 1; i <= END; i ++)) AND NOT ((i = 1; i <= $ END; i ++)); чому немає $ до END?
Baedsch

5
@Baedsch: з тієї ж причини я не використовується як $ i. Сторінка bash man констатує для арифметичної оцінки: "У виразі на змінні оболонки також можна посилатись по імені без використання синтаксису розширення параметра."
користувач3188140

192

обговорення

Використання 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 для Баша (як це я робив зі kshсторінкою чоловіка Korn shell ( ), і це було давно), я пропустив це.

Тому,

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()для підстановки команд ( $(…)конструкції) потрібно a .


1
Єдиним недоліком циклу стилю C є те, що він не може використовувати аргументи командного рядка, оскільки вони починаються з "$".
karatedog

3
@karatedog: for ((i=$1;i<=$2;++i)); do echo $i; doneу сценарії добре працює для bash v.4.1.9, тому я не бачу проблем з аргументами командного рядка. Ви маєте на увазі щось інше?
tzot

Здається, що рішення eval швидше, ніж вбудоване в C-подібне за: $ time for ((i = 1; i <= 100000; ++ i)); робити:; зроблено реальним користувачем 0m21.220s 0m19.763s sys 0m1.203s $ час за i в $ (eval echo "{1..100000}"); робити:; зроблено; реальний користувач 0m13.881s 0m13.536s sys 0m0.152s
Marcin Zaluski

3
Так, але eval - це зло ... @MarcinZaluski time for i in $(seq 100000); do :; doneнабагато швидше!
Ф. Хаурі

Продуктивність повинна бути платформою, оскільки версія eval є найшвидшою на моїй машині.
Ендрю Прок

103

Ось чому оригінальний вираз не спрацював.

Від man 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


1. Bash - це чудова оболонка, і я використовую її інтерактивно, але я не ставлю башмаси в свої сценарії. Сценарії можуть потребувати більш швидкої оболонки, більш захищеної, більш вбудованої. Можливо, їм потрібно запустити все, що встановлено як / bin / sh, і тоді є всі звичайні аргументи про стандарти. Пам'ятаєш снаряда, він же bashdoor?


13
Я не маю сили, але я би перемістив це трохи вгору за списком, перш за все баш-пуповим оглядом, але відразу після стилю С для оцінки циклу та арифметики.
товариш

2
Наслідком є ​​те, що розширення дужок не економить багато пам’яті порівняно з seqвеликими діапазонами. Наприклад, echo {1..1000000} | wcвиявляється, що відлуння виробляє 1 рядок, мільйон слів і 6,888,896 байт. Спроба seq 1 1000000 | wcвиведе мільйон рядків, мільйон слів і 6 888 896 байт, а також більше, ніж у сім разів швидше, як вимірюється timeкомандою.
Джордж

Примітка. whileРаніше я згадував метод POSIX у своїй відповіді: stackoverflow.com/a/31365662/895245 Але рада, що ви згодні :-)
Ciro Santilli 冠状 病毒 审查 六四 事件 法轮功

Я включив цю відповідь у свою відповідь порівняння ефективності нижче. stackoverflow.com/a/54770805/117471 (Це записка до себе, щоб відстежувати, які з них мені залишилось зробити.)
Бруно Броноський,

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

71

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 冠状 病毒 审查 六四 事件 法轮功

Цитата стосується x, а не всього виразу. $((x + 1))просто чудово.
чепнер

Хоча вони не є портативними та відрізняються від GNU seq(BSD seqдозволяє встановити рядок завершення послідовності за допомогою -t), FreeBSD та NetBSD також мають відповідно seq9,0 та 3.0.
Адріан Гюнтер

@CiroSantilli @chepner $((x+1))і $((x + 1))синтаксичного аналізу точно так же, як і коли аналізатор розмічає x+1буде розділений на 3 лексем: x, +, і 1. xне є дійсним числовим маркером, але це допустиме маркер імені змінної, але x+це не так, отже, розбиття. +є дійсним арифметичним маркером оператора, але +1це не так, тож маркер тут знову розділений. І так далі.
Адріан Гюнтер

Я включив цю відповідь у свою відповідь порівняння ефективності нижче. stackoverflow.com/a/54770805/117471 (Це записка до себе, щоб відстежувати, які з них мені залишилось зробити.)
Бруно Броноський,

35

Ще один шар непрямості:

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

2
+1: також eval 'для i в {1 ..' $ END '}; do ... 'здається, природний спосіб вирішити це питання.
Вільям Перселл

28

Можна використовувати

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

seq передбачає виконання зовнішньої команди, яка зазвичай уповільнює справи.
paxdiablo

9
Це не передбачає виконання зовнішньої команди для кожної ітерації, лише один раз. Якщо час запуску однієї зовнішньої команди - це проблема, ви використовуєте неправильну мову.
Марк Бейкер

1
Отже, це єдиний випадок, коли це має значення? Мені було цікаво, чи є різниця в продуктивності чи якийсь невідомий технічний побічний ефект?
Sqeaky

@Squeaky Це окреме питання whichtis відповів тут: stackoverflow.com/questions/4708549 / ...
tripleee

Я включив цю відповідь у свою відповідь порівняння ефективності нижче. stackoverflow.com/a/54770805/117471 (Це записка до себе, щоб відстежувати, які з них мені залишилось зробити.)
Бруно Броноський,

21

Якщо вам потрібен префікс, вам це може сподобатися

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

це дасть урожай

07
08
09
10
11
12

4
Не printf "%02d\n" $iбуло б легше, ніж printf "%2.0d\n" $i |sed "s/ /0/"?
zb226

19

Якщо ви перебуваєте на BSD / OS X, ви можете використовувати jot замість seq:

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

17

Це добре працює в bash:

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

6
echo $((i++))працює і поєднує його в одну лінію.
Бруно Броноський

1
Це не викликає зайвих розширень. POSIX версія: stackoverflow.com/a/31365662/895245
Чіро Сантіллі冠状病毒审查六四事件法轮功

1
@Ciro, оскільки в цьому питанні конкретно йдеться про bash, і він має тег bash, я думаю, ви, мабуть, виявите, що bash 'розширення' є більш ніж нормально :-)
paxdiablo

@paxdiablo Я не маю на увазі, що це неправильно, але чому б не бути портативним, коли ми можемо ;-)
Ciro Santilli 冠状 病毒 审查 六四 事件 法轮功

В bash, ми можемо просто зробити , while [[ i++ -le "$END" ]]; doщоб зробити приріст (пост-) в тесті
Aaron Макдейд

14

Я поєднав декілька ідей тут і виміряв ефективність.

TL; DR Винос:

  1. seq і {..} справді швидкі
  2. for і while петлі повільні
  3. $( ) повільно
  4. for (( ; ; )) петлі повільніші
  5. $(( )) ще повільніше
  6. Турбуватися про N чисел у пам'яті (seq або {..}) нерозумно (принаймні до 1 млн.)

Це не висновки . Вам доведеться подивитися на код С за кожним із них, щоб зробити висновки. Це більше про те, як ми, як правило, використовуємо кожен із цих механізмів для циклічного перегляду коду. Більшість одиничних операцій досить близькі до тієї ж швидкості, що в більшості випадків це не матиме значення. Але подібний механізм - 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

1
Приємно! Не погоджуйся з резюме. Мені $(seq)здається, приблизно така ж швидкість, як і {a..b}. Крім того, кожна операція займає приблизно однаковий час, тому для кожної ітерації циклу для мене додається близько 4 мкс. Тут операція - це відлуння в тілі, арифметичне порівняння, приріст тощо. Чи є щось із цього дивного? Кого хвилює, скільки часу знадобиться атрибутика петлі, щоб виконати свою роботу - час виконання, швидше за все, буде домінувати над вмістом циклу.
Боббого

@bobbogo ви маєте рацію, це дійсно про кількість операцій. Я оновив свою відповідь, щоб це відобразити. Багато дзвінків, які ми робимо, виконують більше операцій, ніж ми могли очікувати. Я звузив це список зі списку близько 50 тестів, які я провів. Я очікував, що моє дослідження було занадто неприємним навіть для цієї натовпу. Як завжди, я пропоную розставити пріоритети вашим зусиллям з кодування так: Зробити це коротше; Зробити його читабельним; Зробити це швидше; Зробіть його портативним. Часто №1 викликає №3. Не витрачайте час на №4, поки не обов’язково.
Бруно Броноський

8

Я знаю, що це питання 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}


8

Якщо ви хочете залишитися якомога ближче до синтаксису дужок-виразів, спробуйте 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запобігається часто небажана поведінка недійсних діапазонів, що подаються як рядки (наприклад ).

Інші відповіді спрацюють у більшості випадків, але всі вони мають принаймні один із наступних недоліків:

  • Багато хто з них використовує передплатки , що може зашкодити продуктивності, а в деяких системах неможливо .
  • Багато з них покладаються на зовнішні програми. Навітьseq бінарний файл, який повинен бути встановлений для використання, повинен бути завантажений bash і повинен містити програму, яку ви очікуєте, щоб він працював у цьому випадку. Повсюдне чи ні, на це можна покластися набагато більше, ніж лише на саму мову Баша.
  • Рішення, які використовують тільки нативну функцію Bash, як-от @ ephemient's, не працюватимуть в алфавітних діапазонах, наприклад {a..z}; розширення дужки буде. Але питання стосувалося діапазонів чисел , тож це примірник.
  • Більшість з них візуально не схожі на {1..10}синтаксис розширеного діапазону діапазону, тому програми, які використовують обидва, можуть бути трохи складніше читати.
  • @ bobbogo відповідь використовує деякі звичні синтаксису, але робить щось несподіване, якщо $ENDзмінна не є дійсним діапазоном "завантаження" для іншого боку діапазону. Якщо END=a, наприклад, помилка не відбудеться і стенографічне значення {1..a}буде відгукуватися. Така поведінка Баша за замовчуванням - також часто несподівана.

Відмова: Я є автором зв'язаного коду.


7

Замінити {}на (( )):

tmpstart=0;
tmpend=4;

for (( i=$tmpstart; i<=$tmpend; i++ )) ; do 
echo $i ;
done

Врожайність:

0
1
2
3
4

Я включив цю відповідь у свою відповідь порівняння ефективності нижче. stackoverflow.com/a/54770805/117471 (Це записка для себе, щоб відстежувати, які з них мені залишилось зробити.)
Бруно Броноскі,

6

Це все добре, але послідовність імовірно застаріла і більшість працює лише з числовими діапазонами.

Якщо ви вкладете свій цикл для циклу у подвійні лапки, початкові та кінцеві змінні будуть відменені, коли ви будете перегукувати рядок, і ви зможете відправити рядок назад до BASH для виконання. $iпотрібно вийти з \ 's, так що його НЕ оцінюють перед тим, як надсилати в підпакет.

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`

Єдиним "накладним", що це повинно генерувати, має бути другий екземпляр удару, тому він повинен бути придатним для інтенсивних операцій.


5

Якщо ви виконуєте команди оболонок і у вас (як у мене) є фетиш до конвеєрного руху, це добре:

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


3

Є багато способів зробити це, проте ті, які я віддаю перевагу, наведені нижче

Використання seq

Конспект від man seq

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

Синтаксис

Повна команда
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}

Тут перші та останні обов'язкові, але вкл. Необов'язково

Використання лише першого та останнього

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

Використовуючи вкл

$ 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

3

якщо ви не хочете використовувати " 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: У моєму базі все одно немає seqкоманди ' '.

Тестовано на Mac OSX 10.6.8, Bash 3.2.48


0

Це працює в 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"
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.