web-dev-qa-db-fra.com

Exécuter deux séquences dans une boucle

J'essaie d'exécuter 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

aucune idée de comment je peux y arriver?

8
HISI

Vous avez seulement besoin d'expansion d'accolade 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 {} demandent au shell de développer vos séquences dans une liste. Il suffit de laisser un espace entre eux, car le shell divise les listes d’arguments sur celles-ci.

10
Zanna

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 les tâches répétitives, vous pouvez utiliser les fonctions suivantes:

#!/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"
6
pa4080

La réponse de Zanna et La réponse de pa408 sont bons et j'irais probablement avec l'un d'eux dans la plupart des cas. Cela va peut-être de soi, mais par souci d'exhaustivité, je le dirai quand même: vous pouvez charger chaque valeur dans un tableau, puis passer en boucle 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
4
GreenMatt

Boucle sans boucle

La réponse de Zanna est tout à fait correcte et convient parfaitement à bash, mais nous pouvons améliorer cela encore davantage sans utiliser de boucle.

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

Le comportement de printf est tel que si le nombre de ARGUMENTS est supérieur aux contrôles de format dans 'FORMAT STRING', alors printf divisera tous les ARGUMENTS en morceaux égaux et continuera à les ajuster à la chaîne de format jusqu'à ce qu'il soit épuisé par la liste ARGUMENTS.

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

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

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

Ou si nous voulons en faire un style old school:

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 (sur laquelle s'appuie {1..15} {20..25}), 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 détaillée, certaines choses pourraient être raccourcies, mais cela fonctionne. Testé avec ksh, dash, mksh et 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 non seulement imprimer mais aussi faire quelque chose avec ces chiffres), 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 de 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 non-coquille

Juste parce que nous pouvons trouver la solution Python

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

Ou avec un peu de Shell:

bash-4.3$ python3 << EOF
> for i in (*range(16),*range(20,26)):
>    print(i)
> EOF
3