Parcourez deux séquences en une seule boucle

8

J'essaie de parcourir deux séquences dans la même boucle dans mon shell comme ci-dessous:

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

une idée comment je peux y arriver?

HISI
la source
@zanna - ma première pensée est que le booléen "et" est exclusif, ce qui signifie que le résultat sont les nombres qui existent sur les deux ensembles; ce qui est nul dans ce cas. Y a-t-il un «et» inclusif?
ravery
1
@ravery J'ai mis "et" juste pour expliquer ce que je cherche
HISI
2
@YassineSihi - Eh bien, prenez note. De nombreux nouveaux programmeurs trébuchent sur ce point jusqu'à ce qu'ils puissent recycler leur cerveau, car le langage parlé utilise "et" inclusivement mais le logique "et" est exclusif dans la plupart des langages de programmation.
ravery

Réponses:

10

Vous avez seulement besoin d'une extension de soutien pour cela

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

Nous pouvons passer une liste à for( ).for i in x y z; do stuff "$i"; done

Donc, ici, les accolades { }obtiennent le shell pour développer vos séquences dans une liste. Vous avez seulement besoin de mettre un espace entre eux, car le shell divise les listes d'arguments sur ceux-ci.

Zanna
la source
Oui, accolades. . . Et vous n'avez même pas besoin d'une boucle pour ça ^ _0
Sergiy Kolodyazhnyy
@SergiyKolodyazhnyy Je pense qu'ils ne veulent pas seulement echoles chiffres
Zanna
oui, s'ils veulent une sorte d'action, comme des touchfichiers, ils peuvent le faire touch {1..15}.txt {20..25}.txt, aucune boucle n'est nécessaire ici. Mais bien sûr, si c'est plusieurs actions sur le même numéro - OK, cela pourrait utiliser une boucle.
Sergiy Kolodyazhnyy
6

Alternativement, nous pouvons utiliser seq( imprimer une séquence de nombres ), voici deux exemples équivalents:

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

S'il s'agit d'un script, pour des tâches répétitives, vous pouvez utiliser des fonctions:

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

La réponse de Zanna et la réponse de pa4080 sont à la fois bien et je serais probablement aller avec l' un d'eux dans la plupart des cas. Cela va peut-être de soi, mais pour être complet, je le dirai quand même: vous pouvez charger chaque valeur dans un tableau puis boucler sur le tableau. Par exemple:

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
la source
@SergiyKolodyazhnyy: Merci les commentaires. Je suis assez vieux pour que c'est ainsi qu'on m'a enseigné, et je le fais toujours en de rares occasions où j'écris un script shell. Cependant, j'ai mis à jour la réponse pour utiliser un tableau.
GreenMatt
Très bien ! Bonne écriture!
Sergiy Kolodyazhnyy
3

Boucle sans boucle

La réponse de Zanna est absolument correcte et bien adaptée à bash, mais nous pouvons encore améliorer cela sans utiliser de boucle.

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

Le comportement de printfest tel que si le nombre de ARGUMENTSest supérieur aux contrôles de format 'FORMAT STRING', alors tout printfsera divisé ARGUMENTS en morceaux égaux et continuera de les ajuster à la chaîne de formatage jusqu'à ce qu'il soit épuisé ARGUMENTS.

Si nous recherchons la portabilité, nous pouvons utiliser à la printf "%d\n" $(seq 1 15) $(seq 20 25)place

Allons plus loin et plus amusant. Disons que nous voulons effectuer une action plutôt que d'imprimer simplement des nombres. Pour créer des fichiers à partir de cette séquence de nombres, nous pourrions facilement le faire touch {1..15}.txt {20..25}.txt. Et si nous voulons que plusieurs choses se produisent? Nous pourrions également faire quelque chose comme ceci:

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

Ou si nous voulons le faire à l'ancienne:

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

Alternative portable mais verbeuse

Si nous voulons créer une solution de script qui fonctionne avec des shells qui n'ont pas d'expansion d'accolade (ce {1..15} {20..25}sur quoi repose), nous pouvons écrire une simple boucle 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

Bien sûr, cette solution est plus verbeuse, certaines choses pourraient être raccourcies, mais cela fonctionne. Testé avec ksh, dash, mkshet, bien sûr bash.


Boucle de style Bash C

Mais si nous voulions créer une boucle spécifique à bash (pour une raison quelconque, peut-être pas seulement en imprimant mais aussi en faisant quelque chose avec ces nombres), nous pouvons également le faire (essentiellement une version en boucle C de la solution portable):

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

Ou dans un format plus lisible:

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

Comparaison des performances de différentes approches en boucle

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 sans coque

Tout simplement parce que nous pouvons voici la solution Python

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

Ou avec un peu de coquille:

bash-4.3$ python3 << EOF
> for i in (*range(16),*range(20,26)):
>    print(i)
> EOF
Sergiy Kolodyazhnyy
la source
1
Je viens de tester touch $(printf "%d\n" {1..15} {20..25}):-)
pa4080
1
@ pa4080 en fait, bashvous n'en avez même pas besoin $(), juste touch {1..15}.txt {20..25}.txt :) Mais bien sûr, nous pourrions utiliser printf "%d\n{1..15} {20..25} `avec xargssi nous voulions faire plus que de simples touchfichiers. Il y a plusieurs façons de faire les choses et cela rend le script tellement amusant!
Sergiy Kolodyazhnyy