¿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 documentación de 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 del rango con una variable? Esto no funciona
END=5
for i in {1..$END}; do echo $i; done
Que imprime:
{1..5}
for i in {01..10}; do echo $i; done
daría números como01, 02, 03, ..., 10
.myarray=('a' 'b' 'c'); for i in ${!myarray[@]}; do echo $i; done
(observe el signo de exclamación). Es más específico que la pregunta original, pero podría ayudar. Ver expansiones de parámetros bash{jpg,png,gif}
que no se abordan directamente aquí, aunque la respuesta será idéntica. Ver expansión de llaves con variable? [duplicado] que está marcado como un duplicado de este.Respuestas:
editar: prefiero
seq
los otros métodos porque realmente puedo recordarlo;)fuente
seq $END
sería suficiente, ya que el valor predeterminado es comenzar desde 1. Desdeman seq
: "Si se omite FIRST o INCREMENT, el valor predeterminado es 1".El
seq
método es el más simple, pero Bash tiene una evaluación aritmética incorporada.La
for ((expr1;expr2;expr3));
construcción funciona igual quefor (expr1;expr2;expr3)
en C y lenguajes similares, y como en otros((expr))
casos, Bash los trata como aritméticos.fuente
seq
. Úsalo!#!/bin/bash
la primera línea de tu script. wiki.ubuntu.com/…discusión
Usar
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 amigable 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, dado quei
es una variable de texto, las conversiones continuas de un lado a otro se realizan con una desaceleración asociada.aritmética de enteros
Esta es una versión mejorada del bucle Bash:
Si lo único que queremos es el
echo
, entonces podríamos escribirecho $((i++))
.Ephemient me enseñó algo: Bash permite
for ((expr;expr;expr))
construcciones. Como nunca leí toda la página de manual de Bash (como lo hice con laksh
página de manual de Korn shell ( ), y eso fue hace mucho tiempo), me lo perdí.Entonces,
parece ser la forma más eficiente de memoria (no será necesario asignar memoria para consumir
seq
la salida, lo que podría ser un problema si END es muy grande), aunque probablemente no sea el "más rápido".la pregunta inicial
eschercycle señaló que la notación Bash { a .. b } funciona solo con literales; cierto, de acuerdo con el manual de Bash. Uno puede superar este obstáculo con un solo (interno)
fork()
sin unexec()
(como es el caso con la llamadaseq
, que ser otra imagen requiere un tenedor + ejecutivo):Ambos
eval
yecho
son Bash incorporados, perofork()
se requiere a para la sustitución del comando (la$(…)
construcción).fuente
for ((i=$1;i<=$2;++i)); do echo $i; done
en un script funciona bien para mí en bash v.4.1.9, por lo que no veo un problema con los argumentos de la línea de comandos. ¿Te refieres a algo más?time for i in $(seq 100000); do :; done
es mucho más rápido!Aquí es por qué la expresión original no funcionó.
De man bash :
Entonces, la expansión de llaves es algo que se hace temprano como una operación macro puramente textual, antes de la expansión de parámetros.
Los 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.
Sugeriría seguir con las características de Posix 1 . Esto significa usar
for i in <list>; do
, si la lista ya se conoce, de lo contrario, usarwhile
oseq
, como en:1. Bash es un gran shell y lo uso de forma interactiva, pero no pongo bash-isms en mis scripts. Los scripts pueden necesitar un shell más rápido, uno más seguro y uno más integrado. Es posible que necesiten ejecutarse en lo que esté instalado como / bin / sh, y luego están todos los argumentos habituales a favor de los estándares. ¿Recuerdas shellshock, también conocido como bashdoor?
fuente
seq
grandes rangos. Por ejemplo,echo {1..1000000} | wc
revela que el eco produce 1 línea, un millón de palabras y 6.888.896 bytes. Intentarseq 1 1000000 | wc
produce un millón de líneas, un millón de palabras y 6,888,896 bytes y también es más de siete veces más rápido, según lo medido por eltime
comando.while
método POSIX anteriormente en mi respuesta: stackoverflow.com/a/31365662/895245 Pero me alegra que esté de acuerdo :-)La manera POSIX
Si le importa la portabilidad, use el ejemplo del estándar POSIX :
Salida:
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 variables como se menciona en el manual de Bash .let i=i+1
: POSIX 7 2. El lenguaje de comandos de Shell no contiene la palabralet
y falla enbash --posix
4.3.42el dólar
i=$i+1
podría ser requerido, pero no estoy seguro. POSIX 7 2.6.4 La expansión aritmética dice:pero leerlo literalmente no implica que se
$((x+1))
expanda yax+1
que no es una variable.fuente
x
, no a toda la expresión.$((x + 1))
está bienseq
(BSD leseq
permite establecer una secuencia de terminación de secuencia con-t
), FreeBSD y NetBSD también tienenseq
desde 9.0 y 3.0, respectivamente.$((x+1))
y$((x + 1))
de análisis exactamente lo mismo, como cuando el analizador tokenizesx+1
será dividido en 3 fichas:x
,+
, y1
.x
no es un token numérico válido, pero es un token de nombre de variable válido, perox+
no lo es, de ahí la división.+
es un token de operador aritmético válido, pero+1
no lo es, por lo que el token se divide nuevamente allí. Etcétera.Otra capa de indirección:
fuente
Puedes usar
fuente
Si necesita un prefijo, entonces le gustaría esto
eso rendirá
fuente
printf "%02d\n" $i
sería más fácil queprintf "%2.0d\n" $i |sed "s/ /0/"
?Si estás en BSD / OS X puedes usar jot en lugar de seq:
fuente
Esto funciona bien en
bash
:fuente
echo $((i++))
funciona y lo combina en una sola línea.bash
, simplemente podemoswhile [[ i++ -le "$END" ]]; do
hacer el incremento (posterior) en la pruebaHe combinado algunas de las ideas aquí y he medido el rendimiento.
TL; DR Para llevar:
seq
y{..}
son realmente rápidosfor
y loswhile
bucles son lentos$( )
es lentofor (( ; ; ))
los bucles son más lentos$(( ))
es aún más lentoEstas no son conclusiones . Tendría que mirar el código C detrás de cada uno de estos para sacar conclusiones. Esto es más acerca de cómo tendemos a usar cada uno de estos mecanismos para recorrer el código. La mayoría de las operaciones individuales están lo suficientemente cerca de ser la misma velocidad que no va a importar en la mayoría de los casos. Pero un mecanismo como
for (( i=1; i<=1000000; i++ ))
es muchas operaciones como puede ver visualmente. También es muchas más operaciones por ciclo de las que obtienefor i in $(seq 1 1000000)
. Y eso puede no ser obvio para usted, por lo que hacer pruebas como esta es valioso.Población
fuente
$(seq)
tiene aproximadamente la misma velocidad que{a..b}
. Además, cada operación lleva aproximadamente el mismo tiempo, por lo que agrega aproximadamente 4 μs a cada iteración del bucle para mí. Aquí una operación es un eco en el cuerpo, una comparación aritmética, un incremento, etc. ¿Algo de esto es sorprendente? A quién le importa cuánto tiempo lleva la parafernalia del bucle para hacer su trabajo: es probable que el tiempo de ejecución esté dominado por el contenido del bucle.Sé que esta pregunta se trata
bash
, pero, solo para el registro,ksh93
es más inteligente y la implementa como se esperaba:fuente
Esta es otra forma:
fuente
Si desea permanecer lo más cerca posible de la sintaxis de la expresión de llaves, pruebe la
range
función de bash-tricks 'range.bash
.Por ejemplo, todo lo siguiente hará exactamente lo mismo que
echo {1..10}
:Intenta admitir la sintaxis nativa de bash con la menor cantidad posible de "problemas": no solo se admiten variables, sino
for i in {1..a}; do echo $i; done
que también se evita el comportamiento a menudo indeseable de rangos no válidos que se suministran como cadenas (por ejemplo ).Las otras respuestas funcionarán en la mayoría de los casos, pero todas tienen al menos uno de los siguientes inconvenientes:
seq
es un binario que debe instalarse para usarse, debe cargarse mediante bash y debe contener el programa que espera para que funcione en este caso. Ubicuo o no, es mucho más en lo que confiar que solo el lenguaje Bash en sí.{a..z}
; la expansión de la abrazadera lo hará. Sin embargo, la pregunta era sobre rangos de números , por lo que esta es una objeción.{1..10}
sintaxis de rango expandido, por lo que los programas que usan ambos pueden ser un poco más difíciles de leer.$END
variable no es un "bookend" de rango válido para el otro lado del rango. SiEND=a
, por ejemplo, no se produce un error y{1..a}
se repite el valor literal . Este es también el comportamiento predeterminado de Bash: a menudo es inesperado.Descargo de responsabilidad: soy el autor del código vinculado.
fuente
Reemplazar
{}
con(( ))
:Rendimientos:
fuente
Todos estos son agradables, pero supuestamente seq 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 final se desreferenciarán cuando haga eco de la cadena, y puede enviar la cadena de regreso a BASH para su ejecución.
$i
debe escaparse con \ 's para que NO se evalúe antes de enviarse a la subshell.Esta salida también se puede asignar a una variable:
La única "sobrecarga" que esto debería generar debería ser la segunda instancia de bash, por lo que debería ser adecuada para operaciones intensivas.
fuente
Si está haciendo comandos de shell y usted (como yo) tiene un fetiche por canalizar, este es bueno:
seq 1 $END | xargs -I {} echo {}
fuente
Hay muchas formas de hacer esto, sin embargo, las que prefiero se dan a continuación
Utilizando
seq
Comando completo
seq first incr last
Ejemplo:
Solo con primero y último:
Solo con el último:
Utilizando
{first..last..incr}
Aquí primero y último son obligatorios e incr es opcional
Usando solo el primero y el último
Usando incr
Puedes usar esto incluso para los personajes como a continuación
fuente
si no quiere usar '
seq
' o 'eval
' o eljot
formato de expansión aritmética, por ejemplo.for ((i=1;i<=END;i++))
, u otros bucles, por ejemplo.while
, y no quiere 'printf
' y está contento 'echo
' solo, entonces esta solución simple podría ajustarse a su presupuesto:a=1; b=5; d='for i in {'$a'..'$b'}; do echo -n "$i"; done;' echo "$d" | bash
PD: Mi bash no tiene
seq
el comando ' ' de todos modos.Probado en Mac OSX 10.6.8, Bash 3.2.48
fuente
Esto funciona en Bash y Korn, también puede ir de mayor a menor número. Probablemente no sea el más rápido o el más bonito, pero funciona lo suficientemente bien. Maneja negativos también.
fuente