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

7 голосов
/ 19 сентября 2013

Я знаю, что этот вопрос о 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}
7 голосов
/ 27 июля 2017

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

Отказ от ответственности: я являюсь автором связанного кода.

6 голосов
/ 19 февраля 2019

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

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
6 голосов
/ 17 августа 2011

Это все хорошо, но 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

Этот вывод также может быть присвоен переменной:

VAR=`echo -e "for i in {$RANGE_START..$RANGE_END}; do echo \\${i}; done" | bash`

Единственные "издержки", которые это должно генерировать, должны быть вторым экземпляром bash, поэтому он должен подходить для интенсивных операций.

6 голосов
/ 12 июля 2015

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

end=5
for i in $(bash -c "echo {1..${end}}"); do echo $i; done
6 голосов
/ 12 марта 2014

Заменить {} на (( )):

tmpstart=0;
tmpend=4;

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

Урожайность:

0
1
2
3
4
5 голосов
/ 27 марта 2017

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

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

0 голосов
/ 27 мая 2019

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

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

Синопсис от man seq

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

Синтаксис

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

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

Пример: * * тысяча двадцать-восемь

$ 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}

Здесь первый и последний являются обязательными, а 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
0 голосов
/ 11 апреля 2018

Это работает в Баш и Корне, также может идти от более высоких к более низким числам. Вероятно, не самый быстрый или самый красивый, но работает достаточно хорошо. Обрабатывает и негативы.

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"
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...