Выполнить две последовательности в одном цикле

8

Я пытаюсь запустить две последовательности в одном цикле в моей оболочке, как показано ниже:

#!/bin/bash
for i in (1..15) and (20..25) ;
do
     echo $i
     ......
     .....other process
done

Есть идеи, как мне этого добиться?

Hisi
источник
@zanna - моя первая мысль заключается в том, что логическое «и» является исключительным, что означает, что результатом являются числа, которые существуют в обоих наборах; чего нет в этом случае. Есть ли включающее «и»?
Ревери
1
@ravery я поставил «и» просто объяснить , что я ищу
Hisi
2
@YassineSihi - Хорошо, запишите. Многие новые программисты сталкиваются с этой проблемой до тех пор, пока не смогут переучить свой мозг, потому что разговорный язык использует "и" включительно, но логично "и" является исключительным в большинстве языков программирования.
Ревери

Ответы:

10

Вам нужно только расширение скобки для этого

$ for n in {1..3} {200..203}; do echo $n; done
1
2
3
200
201
202
203

Мы можем передать список for( ).for i in x y z; do stuff "$i"; done

Итак, здесь фигурные скобки { }получают оболочку для расширения ваших последовательностей в список. Вам нужно только поставить пробел между ними, так как оболочка разделяет списки аргументов на них.

Занна
источник
Да, брекеты. , , И вам даже не нужна петля для этого ^ _0
Сергей Колодяжный
@SergiyKolodyazhnyy Я полагаю, что они на самом деле не хотят просто echoцифры
Zanna
да, если они хотят какое-то действие, например, touchфайлы, они могут просто сделать touch {1..15}.txt {20..25}.txt, никакой цикл здесь не нужен. Но, конечно, если это несколько действий на одном номере - хорошо, это может использовать цикл.
Сергей Колодяжный,
6

В качестве альтернативы мы можем использовать seq( вывести последовательность чисел ), вот два эквивалентных примера:

for i in `seq 1 3` `seq 101 103`; do echo $i; done
for i in $(seq 1 3) $(seq 101 103); do echo $i; done

Если это скрипт, для повторяющихся задач вы можете использовать функции:

#!/bin/bash
my_function() { echo "$1"; }
for i in {1..3}; do my_function "$i"; done
for i in {101..103}; do my_function "$i"; done
#!/bin/bash
my_function() { for i in `seq $1 $2`; do echo "$i"; done; }
my_function "1" "3"
my_function "101" "103"
pa4080
источник
4

Ответ Žanna в и ответ pa4080 игровых оба хороши , и я бы , вероятно , пойти с одним из них в большинстве случаев. Возможно, само собой разумеется, но ради полноты я все равно скажу: вы можете загрузить каждое значение в массив, а затем выполнить цикл по массиву. Например:

the_array=( 1 2 3 4 5 6 7 8 9 10 20 21 22 23 24 25 )
for i in "${the_array[@]}";
do
    echo $i
done
GreenMatt
источник
@SergiyKolodyazhnyy: Спасибо за отзыв. Я достаточно взрослый, чтобы меня так учили, и все же обычно делаю это в редких случаях, когда пишу сценарий оболочки. Однако я обновил ответ, чтобы использовать массив.
GreenMatt
Очень хорошо ! Удачного сценария!
Сергей Колодяжный
3

Цикл без петли

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

printf "%d\n"  {1..15} {20..25}

Поведение printfтаково, что если число ARGUMENTSбольше, чем в элементах управления форматированием 'FORMAT STRING', то оно printfбудет разбивать все ARGUMENTS на равные куски и продолжать их подгонять к строке формата снова и снова, пока не закончится ARGUMENTSсписок.

Если мы стремимся к мобильности, мы можем использовать printf "%d\n" $(seq 1 15) $(seq 20 25)вместо

Давайте продолжим и повеселимся. Скажем, мы хотим выполнить действие, а не просто печатать цифры. Для создания файлов из этой последовательности чисел мы могли бы легко это сделать touch {1..15}.txt {20..25}.txt. Что если мы хотим, чтобы происходило несколько вещей? Мы также могли бы сделать что-то вроде этого:

$ printf "%d\n" {1..15} {20..25} | xargs -I % bash -c 'touch "$1.txt"; stat "$1.txt"' sh %

Или, если мы хотим сделать это в стиле старой школы:

printf "%d\n" {1..15} {20..25} | while read -r line; do 
    touch "$line".txt;
    stat "$line".txt;
    rm "$line".txt; 
done

Портативная, но многословная альтернатива

Если мы хотим создать скрипт-решение, которое работает с оболочками, не имеющими расширения скобок (на что {1..15} {20..25}опирается), мы можем написать простой цикл while:

#!/bin/sh
start=$1
jump=$2
new_start=$3
end=$4

i=$start
while [ $i -le $jump ]
do
    printf "%d\n" "$i"
    i=$((i+1))
    if [ $i -eq $jump ] && ! [ $i -eq $end ];then
        printf "%d\n" "$i"
        i=$new_start
        jump=$end
    fi
done

Конечно, это решение более многословно, некоторые вещи могут быть сокращены, но оно работает. Испытано с ksh, dash, mkshи, конечно же bash.


Bash C-стиль петли

Но если мы хотим сделать цикл специфичным для bash (по какой-то причине, возможно, не просто печатать, но и делать что-то с этими числами), мы также можем сделать это (в основном, версия C-loop портативного решения):

last=15; for (( i=1; i<=last;i++ )); do printf "%d\n" "$i"; [[ $i -eq $last ]] && !  [[ $i -eq 25 ]] && { i=19;last=25;} ;done

Или в более читаемом формате:

last=15
for (( i=1; i<=last;i++ )); 
do 
    printf "%d\n" "$i"
    [[ $i -eq $last ]] && !  [[ $i -eq 25 ]] && { i=19;last=25;} 
done

Сравнение производительности различных циклических подходов

bash-4.3$ time bash -c 'printf "%d\n" {0..50000}>/dev/null'

real    0m0.196s
user    0m0.124s
sys 0m0.028s
bash-4.3$ time bash -c 'for i in {1..50000}; do echo $i > /dev/null; done'

real    0m1.819s
user    0m1.328s
sys 0m0.476s
bash-4.3$ time bash -c ' i=0;while [ $i -le 50000 ]; do echo $i>/dev/null; i=$((i+1)); done'

real    0m3.069s
user    0m2.544s
sys 0m0.500s
bash-4.3$ time bash -c 'for i in $(seq 1 50000); do printf "%d\n" > /dev/null; done'

real    0m1.879s
user    0m1.344s
sys 0m0.520s

Альтернатива без оболочки

Просто потому, что мы можем вот решение Python

$ python3 -c 'print("\n".join([str(i) for i in (*range(1,16),*range(20,26))]))'

Или с небольшим количеством раковины:

bash-4.3$ python3 << EOF
> for i in (*range(16),*range(20,26)):
>    print(i)
> EOF
Сергей Колодяжный
источник
1
Я только что проверил touch $(printf "%d\n" {1..15} {20..25}):-)
pa4080
1
@ pa4080 на самом деле bashвам там даже не нужно $(), просто touch {1..15}.txt {20..25}.txt :) Но, конечно, мы могли бы использовать printf "%d\n{1..15} {20..25} `с, xargsесли мы хотим делать больше, чем просто touchфайлы. Есть много способов сделать что-то, и это делает скрипты такими забавными!
Сергей Колодяжный