Führen Sie zwei Sequenzen in einer Schleife durch

8

Ich versuche, zwei Sequenzen in derselben Schleife in meiner Shell wie folgt zu durchlaufen:

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

Irgendeine Idee, wie ich das erreichen kann?

HISI
quelle
@zanna - mein erster Gedanke ist, dass das boolesche "und" exklusiv ist, was bedeutet, dass das Ergebnis die Zahlen sind, die auf beiden Mengen existieren; Das ist in diesem Fall keine. Gibt es ein inklusive "und"?
Ravery
1
@ravery Ich habe "und" gesetzt, nur um zu erklären, wonach ich suche
HISI
2
@ YassineSihi - Gut notieren. Viele neue Programmierer stolpern über diesen Punkt, bis sie ihr Gehirn neu trainieren können, da die gesprochene Sprache "und" einschließlich, aber das logische "und" in den meisten Programmiersprachen exklusiv verwendet.
Ravery

Antworten:

10

Dafür brauchen Sie nur eine Klammererweiterung

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

Wir können eine Liste an for( ) übergeben.for i in x y z; do stuff "$i"; done

Hier erhalten geschweifte Klammern { }die Shell, um Ihre Sequenzen zu einer Liste zu erweitern. Sie müssen nur ein Leerzeichen dazwischen setzen, da die Shell Listen mit Argumenten auf diese aufteilt.

Zanna
quelle
Ja, Zahnspangen. . . Und Sie brauchen nicht einmal eine Schleife dafür ^ _0
Sergiy Kolodyazhnyy
@SergiyKolodyazhnyy Ich denke, sie wollen eigentlich nicht nur echodie Zahlen
Zanna
Ja, wenn sie eine Aktion wie touchDateien ausführen möchten , können sie dies einfach tun touch {1..15}.txt {20..25}.txt, hier wird keine Schleife benötigt. Aber wenn es sich um mehrere Aktionen auf derselben Nummer handelt - OK, könnte dies eine Schleife verwenden.
Sergiy Kolodyazhnyy
6

Alternativ können wir verwenden seq( eine Folge von Zahlen drucken ), hier sind zwei äquivalente Beispiele:

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

Wenn es sich um ein Skript handelt, können Sie für sich wiederholende Aufgaben folgende Funktionen verwenden:

#!/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
quelle
4

Zannas Antwort und die Antwort von pa4080 sind beide gut und ich würde wahrscheinlich unter den meisten Umständen mit einem von ihnen gehen. Vielleicht ist es selbstverständlich, aber der Vollständigkeit halber werde ich es trotzdem sagen: Sie können jeden Wert in ein Array laden und dann das Array durchlaufen. Zum Beispiel:

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
quelle
@SergiyKolodyazhnyy: Danke für das Feedback. Ich bin alt genug, dass ich so unterrichtet wurde, und mache es normalerweise immer noch in den seltenen Fällen, in denen ich ein Shell-Skript schreibe. Ich habe die Antwort jedoch aktualisiert, um ein Array zu verwenden.
GreenMatt
Sehr gut ! Viel Spaß beim Scripting!
Sergiy Kolodyazhnyy
3

Schleifen ohne Schleife

Zannas Antwort ist absolut richtig und gut für Bash geeignet, aber wir können das noch weiter verbessern, ohne eine Schleife zu verwenden.

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

Das Verhalten von printfist so, dass, wenn die Anzahl ARGUMENTSgrößer als die Formatsteuerelemente in ist 'FORMAT STRING', printfalle ARGUMENTS in gleiche Blöcke aufgeteilt werden und diese immer wieder an die Formatzeichenfolge angepasst werden, bis die ARGUMENTSListe leer ist.

Wenn wir nach Portabilität streben, können wir printf "%d\n" $(seq 1 15) $(seq 20 25)stattdessen verwenden

Lassen Sie uns das weiter gehen und mehr Spaß machen. Angenommen, wir möchten eine Aktion ausführen, anstatt nur Zahlen zu drucken. Um Dateien aus dieser Zahlenfolge zu erstellen, könnten wir dies leicht tun touch {1..15}.txt {20..25}.txt. Was ist, wenn mehrere Dinge auftreten sollen? Wir könnten auch so etwas machen:

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

Oder wenn wir es im Old-School-Stil machen wollen:

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

Tragbare, aber ausführliche Alternative

Wenn wir eine Skriptlösung erstellen möchten, die mit Shells ohne Klammererweiterung funktioniert (worauf es {1..15} {20..25}ankommt), können wir eine einfache while-Schleife schreiben:

#!/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

Natürlich ist diese Lösung ausführlicher, einige Dinge könnten verkürzt werden, aber es funktioniert. Getestet mit ksh, dash, mkshund natürlich bash.


Bash C-Schlaufe

Aber wenn wir eine Schleife bash-spezifisch machen wollten (aus welchem ​​Grund auch immer, vielleicht nicht nur drucken, sondern auch etwas mit diesen Zahlen machen), können wir dies auch tun (im Grunde C-Loop-Version der tragbaren Lösung):

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

Oder in besser lesbarem Format:

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

Leistungsvergleich verschiedener Schleifenansätze

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

Alternative ohne Shell

Nur weil wir hier die Python-Lösung können

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

Oder mit etwas Muschel:

bash-4.3$ python3 << EOF
> for i in (*range(16),*range(20,26)):
>    print(i)
> EOF
Sergiy Kolodyazhnyy
quelle
1
Ich habe gerade getestet touch $(printf "%d\n" {1..15} {20..25}):-)
pa4080
1
@ pa4080 eigentlich für bashdich nicht mal $()da, nur touch {1..15}.txt {20..25}.txt :) Aber natürlich könnten wir printf "%d\n{1..15} {20..25} `mit verwenden, xargswenn wir mehr als nur touchDateien machen wollen. Es gibt viele Möglichkeiten, Dinge zu tun, und es macht das Schreiben von Skripten so viel Spaß!
Sergiy Kolodyazhnyy