Esegui due sequenze in un ciclo

8

Sto provando a eseguire 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 su come posso ottenere questo?

    
posta HISI 24.10.2017 - 14:52

4 risposte

10

Per questo è necessaria solo l'espansione delle bretelle

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

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

Quindi qui, le parentesi graffe { } consentono alla shell di espandere le sequenze in un elenco. Hai solo bisogno di mettere uno spazio tra di loro, dal momento che la shell divide gli elenchi di argomenti su quelli.

    
risposta data Zanna 24.10.2017 - 15:18
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 ""; }
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  '; do echo "$i"; done; }
my_function "1" "3"
my_function "101" "103"
    
risposta data pa4080 24.10.2017 - 15:28
4

La risposta di Zanna e la risposta di pa4080 è buona e probabilmente andrei con uno di questi nella maggior parte delle circostanze. Forse è ovvio, ma per completezza, lo dirò comunque: è possibile caricare ciascun valore in una matrice e quindi eseguire il loop sull'array. Ad 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
    
risposta data GreenMatt 24.10.2017 - 17:15
3

Loop senza loop

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

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

Il comportamento di printf è tale che se il% di ARGUMENTS è maggiore dei controlli di formato in 'FORMAT STRING' , allora printf dividerà tutto ARGUMENTS in blocchi uguali e continuerai a montarli sulla stringa di formato su e finchè non esaurisce ARGUMENTS list.

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

Prendiamo questo più lontano e più divertente. Diciamo che vogliamo eseguire un'azione anziché semplicemente stampare numeri. Per creare file da quella sequenza di numeri, potremmo facilmente fare touch {1..15}.txt {20..25}.txt . Cosa succede se vogliamo che accadano più cose? Potremmo anche fare qualcosa di simile:

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

O se vogliamo renderlo stile old school:

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 l'espansione delle parentesi graffe (che è ciò su cui si basa {1..15} {20..25} ), possiamo scrivere un ciclo while semplice:

#!/bin/sh
start=
jump=
new_start=
end=

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

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

Ciclo di stile B di Bash

Ma se volessimo creare un loop specifico per bash (per qualsiasi ragione, forse non solo per la stampa ma anche per fare qualcosa con quei numeri), possiamo anche farlo (in pratica la 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 loop

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 trovare 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
    
risposta data Sergiy Kolodyazhnyy 26.10.2017 - 21:41

Leggi altre domande sui tag