循环执行两个序列


8

我正在尝试在外壳程序的同一循环中遍历两个序列,如下所示:

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

任何想法我怎么能做到这一点?


@zanna-我的第一个想法是布尔值“ and”是互斥的,这意味着结果是两个集合中都存在的数字;在这种情况下没有。有一个包含性的“和”吗?
ravery

1
@ravery我输入“ and”只是为了解释我在寻找什么
-HISI

2
@YassineSihi-好了做笔记。许多新程序员在开始重新训练大脑之前就迷失了方向,因为口头语言使用“ and”(包括),但逻辑“ and”在大多数编程语言中是排他的。
极了

Answers:


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

因此,在这里,花括号{ }使外壳将序列扩展为列表。您只需要在它们之间放置一个空格,因为shell会拆分这些参数的列表。


是的,大括号。。。而且您甚至不需要为此循环^ _0
Sergiy Kolodyazhnyy 17-10-26

@SergiyKolodyazhnyy我认为他们实际上并不只是想要echo数字
Zanna

是的,如果他们想执行某种操作(例如touch文件),则可以这样做touch {1..15}.txt {20..25}.txt,这里不需要循环。但是,当然,如果在同一数字上有多个动作-好的,那可以使用循环。
Sergiy Kolodyazhnyy

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"

4

Zanna的答案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

@SergiyKolodyazhnyy:感谢反馈。我已经足够老了,这就是我所教的方式,并且通常在极少数情况下我正在编写Shell脚本时才这样做。但是,我已经更新了答案以使用数组。
GreenMatt

很好 !祝脚本编写愉快!
Sergiy Kolodyazhnyy

3

无循环循环

Zanna的答案是绝对正确的,非常适合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

当然,此解决方案更为冗长,可以缩短某些步骤,但是可以使用。测试了kshdashmksh,当然bash


Bash C风格的循环

但是,如果我们想创建特定于循环bash的循环(出于某种原因,也许不仅是打印,而且还要使用这些数字进行操作),我们也可以这样做(基本上是便携式解决方案的C循环版本):

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\nxargs那么当然可以使用{1..15} {20..25}` touch。有许多种做事的方法,它使编写脚本变得如此有趣!
Sergiy Kolodyazhnyy
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.