Pregunta ¿Cómo puedo iterar sobre un rango de números definidos por variables en Bash?


¿Cómo puedo iterar 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 el Bash documentación)

 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 del rango con una variable? Esto no funciona:

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

Que impresiones:

{1..5}


1056
2017-10-04 01:38


origen


Respuestas:


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

editar: prefiero seq sobre los otros métodos porque realmente puedo recordarlo;)


1186
2017-10-04 01:41



los seq método 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

los for ((expr1;expr2;expr3)); construir funciona igual for (expr1;expr2;expr3) en C y en idiomas similares, y como cualquier otro ((expr)) casos, Bash los trata como aritmética.


309
2017-10-04 21:43



discusión

Utilizando seq está bien, como sugirió Jiaaro. Pax Diablo sugirió un bucle Bash para evitar llamar a un subproceso, con la ventaja adicional de ser más compatible con la memoria si $ END es demasiado grande. Zathrus detectó un error típico en la implementación del bucle, y también insinuó que desde i es una variable de texto, las conversiones continuas de ida y vuelta se realizan con una desaceleración asociada.

Aritmética entera

Esta es una versión mejorada del bucle Bash:

typeset -i i END
let END=5 i=1
while ((i<=END)); do
    echo $i
    …
    let i++
done

Si lo único que queremos es el echo, entonces podríamos escribir echo $((i++)).

efusivo me enseñó algo: Bash permite for ((expr;expr;expr)) construcciones Ya que nunca he leído la página completa de hombres de Bash (como he hecho con el caparazón de Korn (ksh) página de manual, y eso fue hace mucho tiempo), me lo perdí.

Asi que,

typeset -i i END # Let's be explicit
for ((i=1;i<=END;++i)); do echo $i; done

parece ser la forma más eficiente de la memoria (no será necesario asignar memoria para consumir) seqde salida, que podría ser un problema si END es muy grande), aunque probablemente no sea el "más rápido".

la pregunta inicial

eschercycle notó que el {un..segundo} La notación Bash solo funciona con literales; cierto, de acuerdo con el manual de Bash. Uno puede superar este obstáculo con un solo (interno) fork() sin un exec() (como es el caso de llamar seq, que siendo otra imagen requiere un fork + exec):

for i in $(eval echo "{1..$END}"); do

Ambos eval y echo son Bash builtins, pero una fork() es requerido para la sustitución del comando (el $(…) construir).


160
2017-10-04 02:38



Esta es la razón por la cual la expresión original no funcionó.

De hombre bash:

La expansión de la abrazadera se realiza antes   otras expansiones, y cualquier   personajes especiales a otros   expansiones se conservan en el   resultado. Es estrictamente textual. Intento   no aplica ningún sintáctico   interpretación en el contexto de   la expansión o el texto entre el   tirantes.

Asi que, expansión de la abrazadera es algo hecho temprano como una operación macro puramente textual, antes expansión de parámetros

Las shells son híbridos altamente optimizados entre macroprocesadores y lenguajes de programación más formales. Para optimizar los casos de uso típicos, el lenguaje se hace bastante más complejo y se aceptan algunas limitaciones.

Recomendación

Sugeriría seguir con Posix1 caracteristicas. Esto significa usar for i in <list>; do, si la lista ya es conocida, de lo contrario, use while o seq, como en:

#!/bin/sh

limit=4

i=1; while [ $i -le $limit ]; do
  echo $i
  i=$(($i + 1))
done
# Or -----------------------
for i in $(seq 1 $limit); do
  echo $i
done


1. Bash es un gran caparazón y lo uso de forma interactiva, pero no coloco bash-isms en mis scripts. Los scripts pueden necesitar un shell más rápido, uno más seguro y otro más incrustado. Es posible que necesiten ejecutar lo que esté instalado como / bin / sh, y luego están todos los argumentos pro estándares habituales. Recuerda neurosis de guerra, alias Bashdoor? 


81
2018-04-19 22:32



La forma POSIX

Si le importa la portabilidad, use la 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 son no POSIX:


43
2017-07-12 07:54



Otra capa de indirección:

for i in $(eval echo {1..$END}); do
    ∶

28
2018-03-14 19:51



Puedes usar

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

21
2017-10-04 01:41



Si está utilizando BSD / OS X, puede usar jot en lugar de seq:

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

18
2018-03-15 23:29