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?


@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.


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"

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

@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

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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.