shell español - ¿Cómo itero en un rango de números definidos por variables en Bash?




programas if (15)

¿Cómo itero sobre un rango de números en Bash cuando el rango viene dado por una variable?

Sé que puedo hacer esto (llamado "expresión de secuencia" en la documentation Bash):

 for i in {1..5}; do echo $i; done

Lo que da:

1
2
3
4
5

Sin embargo, ¿cómo puedo reemplazar cualquiera de los puntos finales de rango con una variable? Esto no funciona:

END=5
for i in {1..$END}; do echo $i; done

Que imprime:

{1..5}


Answers

for i in $(seq 1 $END); do echo $i; done

Edición: prefiero seq sobre los otros métodos porque realmente puedo recordarlo;)


Puedes usar

for i in $(seq $END); do echo $i; done

Si estás en BSD / OS X puedes usar jot en lugar de seq:

for i in $(jot $END); do echo $i; done

Esto funciona en Bash y Korn, también puede ir de números más altos a más bajos. Probablemente no sea el más rápido o el más bonito, pero funciona lo suficientemente bien. Maneja los negativos también.

function num_range {
   # Return a range of whole numbers from beginning value to ending value.
   # >>> num_range start end
   # start: Whole number to start with.
   # end: Whole number to end with.
   typeset s e v
   s=${1}
   e=${2}
   if (( ${e} >= ${s} )); then
      v=${s}
      while (( ${v} <= ${e} )); do
         echo ${v}
         ((v=v+1))
      done
   elif (( ${e} < ${s} )); then
      v=${s}
      while (( ${v} >= ${e} )); do
         echo ${v}
         ((v=v-1))
      done
   fi
}

function test_num_range {
   num_range 1 3 | egrep "1|2|3" | assert_lc 3
   num_range 1 3 | head -1 | assert_eq 1
   num_range -1 1 | head -1 | assert_eq "-1"
   num_range 3 1 | egrep "1|2|3" | assert_lc 3
   num_range 3 1 | head -1 | assert_eq 3
   num_range 1 -1 | tail -1 | assert_eq "-1"
}

Esta es otra manera:

end=5
for i in $(bash -c "echo {1..${end}}"); do echo $i; done

El método seq es el más simple, pero Bash tiene una evaluación aritmética incorporada.

END=5
for ((i=1;i<=END;i++)); do
    echo $i
done
# ==> outputs 1 2 3 4 5 on separate lines

El for ((expr1;expr2;expr3)); las obras de construcción, al igual que for (expr1;expr2;expr3) en C y lenguajes similares, y al igual que en otros ((expr)) casos, Bash los trata como aritméticos.


La forma POSIX

Si le importa la portabilidad, use el ejemplo del estándar POSIX :

i=2
end=5
while [ $i -le $end ]; do
    echo $i
    i=$(($i+1))
done

Salida:

2
3
4
5

Cosas que no son POSIX:

  • (( )) sin dólar, aunque es una extensión común como lo menciona POSIX .
  • [[ . [ es suficiente aquí. Ver también: ¿Cuál es la diferencia entre corchetes simples y dobles en Bash?
  • for ((;;))
  • seq (GNU Coreutils)
  • {start..end} , y eso no puede funcionar con las variables como se menciona en el manual de Bash .
  • let i=i+1 : POSIX 7 2. Shell Command Language no contiene la palabra let , y falla en bash --posix 4.3.42
  • el dólar en i=$i+1 puede ser requerido, pero no estoy seguro. POSIX 7 2.6.4 Expansión aritmética dice:

    Si la variable de shell x contiene un valor que forma una constante entera válida, incluyendo opcionalmente un signo más o menos inicial, entonces las expansiones aritméticas "$ ((x))" y "$ (($ x))" devolverán lo mismo valor.

    pero leerlo literalmente no implica que $((x+1)) expanda ya que x+1 no es una variable.


Todos estos son buenos, pero seq supuestamente está en desuso y la mayoría solo funciona con rangos numéricos.

Si encierra su bucle for entre comillas dobles, las variables de inicio y finalización se eliminarán de las referencias cuando haga eco de la cadena, y puede enviar la cadena de nuevo a BASH para su ejecución. $i necesita ser escapado con \ 's para que NO sea evaluado antes de ser enviado a la subshell.

RANGE_START=a
RANGE_END=z
echo -e "for i in {$RANGE_START..$RANGE_END}; do echo \\${i}; done" | bash

Esta salida también se puede asignar a una variable:

VAR=`echo -e "for i in {$RANGE_START..$RANGE_END}; do echo \\${i}; done" | bash`

La única "sobrecarga" que debe generar debe ser la segunda instancia de bash, por lo que debe ser adecuada para operaciones intensivas.


Si estás haciendo comandos de shell y tú (como yo) tienes un fetiche para canalizar, este es bueno:

seq 1 $END | xargs -I {} echo {}


Si lo necesitas prefijo lo que te guste

 for ((i=7;i<=12;i++)); do echo `printf "%2.0d\n" $i |sed "s/ /0/"`;done

que cederá

07
08
09
10
11
12

Si quieres estar lo más cerca posible de la sintaxis de la expresión de corchetes, prueba la función de range de range.bash de bash-tricks .

Por ejemplo, todo lo siguiente hará exactamente lo mismo que echo {1..10} :

source range.bash
one=1
ten=10

range {$one..$ten}
range $one $ten
range {1..$ten}
range {1..10}

Intenta admitir la sintaxis de bash nativa con la menor cantidad de "errores" posibles: no solo se admiten variables, sino que el comportamiento a menudo indeseable de rangos no válidos se proporciona como cadenas (por ejemplo, for i in {1..a}; do echo $i; done ) se previene también.

Las otras respuestas funcionarán en la mayoría de los casos, pero todas tienen al menos uno de los siguientes inconvenientes:

  • Muchos de ellos utilizan subshells , que pueden dañar el rendimiento y pueden no ser posibles en algunos sistemas.
  • Muchos de ellos dependen de programas externos. Incluso seq es un binario que debe instalarse para ser utilizado, debe estar cargado por bash y debe contener el programa que espera, para que funcione en este caso. Ubicado o no, eso es mucho más en lo que confiar que solo el lenguaje Bash.
  • Las soluciones que solo usan la funcionalidad nativa de Bash, como @phemient's, no funcionarán en rangos alfabéticos, como {a..z} ; refuerzo de la voluntad. Sin embargo, la pregunta era sobre rangos de números , por lo que esta es una objeción.
  • La mayoría de ellos no son visualmente similares a la {1..10} sintaxis de rango ampliado, por lo que los programas que usan ambos pueden ser un poco más difíciles de leer.
  • La respuesta de @bobbogo utiliza parte de la sintaxis familiar, pero hace algo inesperado si la variable $END no es un rango válido de "final de libro" para el otro lado del rango. Si END=a , por ejemplo, no se producirá un error y el valor literal {1..a} repetirá. Este es también el comportamiento predeterminado de Bash, ya que a menudo es inesperado.

Descargo de responsabilidad: Soy el autor del código vinculado.


Sé que esta pregunta es sobre bash , pero, solo para el registro, ksh93 es más inteligente y la implementa como se esperaba:

$ ksh -c 'i=5; for x in {1..$i}; do echo "$x"; done'
1
2
3
4
5
$ ksh -c 'echo $KSH_VERSION'
Version JM 93u+ 2012-02-29

$ bash -c 'i=5; for x in {1..$i}; do echo "$x"; done'
{1..5}

Esto funciona bien en bash :

END=5
i=1 ; while [[ $i -le $END ]] ; do
    echo $i
    ((i = i + 1))
done

Reemplace {} con (( )) :

tmpstart=0;
tmpend=4;

for (( i=$tmpstart; i<=$tmpend; i++ )) ; do 
echo $i ;
done

Rendimientos:

0
1
2
3
4

Si no te importa procesarlos inmediatamente, me gusta hacer esto:

for i in $(echo $IN | tr ";" "\n")
do
  # process
done

Podría usar este tipo de bucle para inicializar una matriz, pero probablemente haya una forma más fácil de hacerlo. Espero que esto ayude, sin embargo.





bash shell for-loop syntax