Ejecuta dos secuencias en un bucle

8

Estoy tratando de ejecutar dos secuencias en el mismo bucle en mi shell como a continuación:

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

¿Alguna idea de cómo puedo lograr esto?

HISI
fuente
@zanna: mi primer pensamiento es que el booleano "y" es exclusivo, lo que significa que el resultado son los números que existen en ambos conjuntos; que no es ninguno en este caso. ¿Hay un "y" inclusivo?
ravery
1
@ravery puse "y" solo para explicar lo que estoy buscando
HISI
2
@YassineSihi - Bueno, toma nota. Muchos programadores nuevos tropiezan en este punto hasta que pueden volver a entrenar su cerebro, porque el lenguaje hablado usa "e" inclusive pero lo lógico "y" es exclusivo en la mayoría de los lenguajes de programación.
ravery

Respuestas:

10

Solo necesitas expansión de llaves para eso

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

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

Entonces, aquí, las llaves { }obtienen el shell para expandir sus secuencias en una lista. Solo necesita poner un espacio entre ellos, ya que el shell divide listas de argumentos sobre ellos.

Zanna
fuente
Sí, frenillos. . . Y ni siquiera necesitas un bucle para eso ^ _0
Sergiy Kolodyazhnyy
@SergiyKolodyazhnyy Supongo que en realidad no solo quieren echolos números
Zanna
sí, si quieren algún tipo de acción, como toucharchivos, simplemente pueden hacer touch {1..15}.txt {20..25}.txt, no se necesita un bucle aquí. Pero, por supuesto, si se trata de múltiples acciones en el mismo número, está bien, eso podría usar un bucle.
Sergiy Kolodyazhnyy
6

Alternativamente, podemos usar seq( imprimir una secuencia de números ), aquí hay dos ejemplos equivalentes:

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

Si es un script, para tareas repetitivas, puede usar funciones:

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

La respuesta de Zanna y respuesta de pa4080 son buenos y probablemente iría con uno de ellos en la mayoría de las circunstancias. Quizás no hace falta decirlo, pero en aras de la exhaustividad, lo diré de todos modos: puede cargar cada valor en una matriz y luego recorrerla en bucle. Por ejemplo:

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
fuente
@SergiyKolodyazhnyy: Gracias por los comentarios. Soy lo suficientemente mayor como para que eso me enseñaron, y todavía lo hago en la rara ocasión en que escribo un script de shell. Sin embargo, he actualizado la respuesta para usar una matriz.
GreenMatt
Muy bien ! Feliz secuencia de comandos!
Sergiy Kolodyazhnyy
3

Bucle sin bucle

La respuesta de Zanna es absolutamente correcta y adecuada para bash, pero podemos mejorar eso aún más sin utilizar un bucle.

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

El comportamiento de printfes tal que si el número de ARGUMENTSes mayor que los controles de formato 'FORMAT STRING', printflos dividirá ARGUMENTS en trozos iguales y los ajustará a la cadena de formato una y otra vez hasta que se agote la ARGUMENTSlista.

Si nos esforzamos por la portabilidad, podemos utilizar en su printf "%d\n" $(seq 1 15) $(seq 20 25)lugar

Llevemos esto más lejos y más divertido. Digamos que queremos realizar una acción en lugar de solo imprimir números. Para crear archivos a partir de esa secuencia de números, podríamos hacerlo fácilmente touch {1..15}.txt {20..25}.txt. ¿Qué pasa si queremos que ocurran varias cosas? También podríamos hacer algo como esto:

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

O si queremos hacerlo al estilo de la vieja escuela:

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

Alternativa portátil pero detallada

Si queremos crear una solución de script que funcione con shells que no tengan expansión de llaves (que es de lo que {1..15} {20..25}depende), podemos escribir un ciclo while simple:

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

Por supuesto, esta solución es más detallada, algunas cosas podrían acortarse, pero funciona. Probado con ksh, dash, mksh, y, por supuesto bash.


Bash bucle estilo C

Pero si quisiéramos hacer un ciclo específico de bash (por cualquier razón, quizás no solo imprimiendo sino también haciendo algo con esos números), también podemos hacer esto (básicamente la versión C-loop de la solución portátil):

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 en formato más legible:

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

Comparación de rendimiento de diferentes enfoques de bucle

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 sin carcasa

Solo porque podemos, aquí está la solución de Python

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

O con un poco de concha:

bash-4.3$ python3 << EOF
> for i in (*range(16),*range(20,26)):
>    print(i)
> EOF
Sergiy Kolodyazhnyy
fuente
1
Acabo de probar touch $(printf "%d\n" {1..15} {20..25}):-)
pa4080
1
@ pa4080 en realidad para bashusted ni siquiera necesita $()allí, solo touch {1..15}.txt {20..25}.txt :) Pero, por supuesto, podríamos usar printf "%d\n{1..15} {20..25} ` xargssi quisiéramos hacer más que solo toucharchivos. ¡Hay muchas maneras de hacer las cosas y hace que las secuencias de comandos sean muy divertidas!
Sergiy Kolodyazhnyy