Esegui due sequenze in un ciclo


8

Sto cercando di scorrere due sequenze nello stesso loop nella mia shell come di seguito:

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

qualche idea di come posso raggiungere questo obiettivo?


@zanna - il mio primo pensiero è che il booleano "e" sia esclusivo, il che significa che il risultato sono i numeri che esistono su entrambi i set; che non è nessuno in questo caso. Esiste un "e" inclusivo?
Ravery,

1
@ravery ho messo "e" solo per spiegare cosa sto cercando
HISI,

2
@YassineSihi - Bene, prendi nota. Molti nuovi programmatori inciampano su questo punto fino a quando non riescono a riqualificare il cervello, perché il linguaggio parlato usa "e" inclusivamente ma il logico "e" è esclusivo nella maggior parte dei linguaggi di programmazione.
Ravery

Risposte:


10

Per questo è necessario solo un ampliamento del supporto

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

Possiamo passare un elenco a for( ).for i in x y z; do stuff "$i"; done

Quindi, qui, le parentesi graffe { }ottengono la shell per espandere le sequenze in un elenco. Devi solo mettere uno spazio tra di loro, poiché la shell divide gli elenchi di argomenti su quelli.


Sì, parentesi graffe. . . E non hai nemmeno bisogno di un loop per quello ^ _0
Sergiy Kolodyazhnyy,

@SergiyKolodyazhnyy Immagino che in realtà non vogliono solo echoi numeri
Zanna,

sì, se vogliono fare una sorta di azione, come i touchfile, possono semplicemente fare touch {1..15}.txt {20..25}.txt, non è necessario alcun ciclo qui. Ma ovviamente se si tratta di più azioni sullo stesso numero - OK, potrebbe usare un ciclo.
Sergiy Kolodyazhnyy,

6

In alternativa possiamo usare seq( stampare una sequenza di numeri ), qui ci sono due esempi equivalenti:

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

Se si tratta di uno script, per attività ripetitive, è possibile utilizzare le funzioni:

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

La risposta di Zanna e la risposta di pa4080 sono entrambe buone e probabilmente andrei con una di esse nella maggior parte dei casi. Forse è ovvio, ma per completezza, lo dirò comunque: è possibile caricare ciascun valore in un array e quindi passare in rassegna l'array. Per esempio:

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: grazie del feedback. Sono abbastanza vecchio che è così che mi è stato insegnato, e di solito lo faccio ancora nelle rare occasioni in cui scrivo una shell script. Tuttavia, ho aggiornato la risposta per utilizzare un array.
GreenMatt,

Ottimo ! Buona sceneggiatura!
Sergiy Kolodyazhnyy,

3

Loop senza loop

La risposta di Zanna è assolutamente corretta e adatta per bash, ma possiamo migliorarla ancora di più senza utilizzare un loop.

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

Il comportamento di printfè tale che se il numero di ARGUMENTSè maggiore dei controlli di formato in 'FORMAT STRING', allora printfsi divideranno tutti ARGUMENTS in blocchi uguali e continueranno ad adattarli più volte alla stringa di formato fino a quando non si esaurisce l' ARGUMENTSelenco.

Se stiamo cercando la portabilità, possiamo printf "%d\n" $(seq 1 15) $(seq 20 25)invece utilizzare

Prendiamo questo ulteriore e più divertente. Supponiamo di voler eseguire un'azione anziché semplicemente stampare numeri. Per creare file da quella sequenza di numeri, potremmo facilmente farlo touch {1..15}.txt {20..25}.txt. E se vogliamo che accadano più cose? Potremmo anche fare qualcosa del genere:

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

O se vogliamo renderlo in stile vecchia scuola:

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

Alternativa portatile ma dettagliata

Se vogliamo creare una soluzione di script che funzioni con shell che non hanno espansione di controvento (che è ciò {1..15} {20..25}su cui si basa), possiamo scrivere un semplice ciclo 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

Naturalmente questa soluzione è più dettagliata, alcune cose potrebbero essere abbreviate, ma funziona. Testato con ksh, dash, mksh, e, naturalmente bash.


Bash in stile C-loop

Ma se volessimo creare un loop bash specifico (per qualsiasi motivo, forse non solo stampare ma anche fare qualcosa con quei numeri), possiamo anche farlo (fondamentalmente versione C-loop della soluzione portatile):

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

O in un formato più leggibile:

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

Confronto delle prestazioni di diversi approcci di looping

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

Alternativa non shell

Solo perché possiamo ecco la soluzione Python

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

O con un po 'di shell:

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

1
Ho appena provato touch $(printf "%d\n" {1..15} {20..25}):-)
pa4080,

1
@ pa4080 in realtà per bashte non hai nemmeno bisogno $()lì, solo touch {1..15}.txt {20..25}.txt :) Ma ovviamente potremmo usare printf "%d\n{1..15} {20..25} `con xargsse volessimo fare più di un semplice touchfile. Ci sono molti modi per fare le cose e questo rende lo script così divertente!
Sergiy Kolodyazhnyy,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.