¿Hay una mejor manera de ejecutar un comando N veces en bash?

304

De vez en cuando ejecuto una línea de comando bash como esta:

n=0; while [[ $n -lt 10 ]]; do some_command; n=$((n+1)); done

Para ejecutar some_commandvarias veces seguidas, 10 veces en este caso.

A menudo some_commandes realmente una cadena de comandos o una tubería.

¿Hay una manera más concisa de hacer esto?

bstpierre
fuente
1
Además de las otras buenas respuestas, puede usar en let ++nlugar de n=$((n+1))(3 caracteres menos).
musiphil
1
Algunas indicaciones de cuáles de estos métodos son portátiles sería bueno.
Faheem Mitha
serverfault.com/questions/273238/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
3
Si está dispuesto a cambiar las conchas, zshtiene repeat 10 do some_command; done.
chepner
1
@JohnVandivier Acabo de probarlo en bash en un nuevo contenedor Docker 18.04, la sintaxis original como se publicó en mi pregunta todavía funciona. Tal vez esté ejecutando un shell que no sea bash, como / bin / sh, que es realmente un guión, en cuyo caso vuelvo sh: 1: [[: not found.
bstpierre

Respuestas:

479
for run in {1..10}
do
  command
done

O como una línea para aquellos que quieren copiar y pegar fácilmente:

for run in {1..10}; do command; done
Joe Koberg
fuente
19
Si tiene MUCHAS iteraciones, el formulario con for (n=0;n<k;n++))puede ser mejor; Sospecho que {1..k}se materializará una cadena con todos esos enteros separados por espacios.
Joe Koberg
@ Joe Koberg, gracias por el consejo. Normalmente uso N <100, así que esto parece bueno.
bstpierre
10
@bstpierre: el formulario de expansión de llaves no puede usar variables (fácilmente) para especificar el rango en Bash.
Pausado hasta nuevo aviso.
55
Eso es verdad. La expansión de llaves se realiza antes de la expansión de variables de acuerdo con gnu.org/software/bash/manual/bashref.html#Brace-Expansion , por lo que nunca verá los valores de ninguna variable.
Joe Koberg
55
Lo triste de la expansión variable. Estoy usando following para recorrer n veces y tengo números n=15;for i in $(seq -f "%02g" ${n});do echo $i; done 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
formateados
203

Usando una constante:

for ((n=0;n<10;n++)); do some_command; done

Usando una variable (puede incluir expresiones matemáticas):

x=10; for ((n=0; n < (x / 2); n++)); do some_command; done
BatchyX
fuente
44
Esta es la forma más cercana al lenguaje de programación ^^ - ¡debería ser la aceptada!
Nam G VU
Esta es mejor porque es una sola línea y, por lo tanto, es más fácil de usar dentro de la terminal
Kunok
También puede utilizar una variable, por ejemplox=10 for ((n=0; n < $x; n++));
Jelphy
@Kunok Es tan aceptable ejecutar la respuesta aceptada en una línea:for run in {1..10}; do echo "on run $run"; done
Douglas Adams
¿Qué npasa si ya está configurado en un valor específico? for (( ; n<10; n++ ))no funciona / editar: mejor utilizar probablemente una de las otras respuestas, como el while (( n++...uno
phil294
121

Otra forma simple de hackearlo:

seq 20 | xargs -Iz echo "Hi there"

ejecutar echo 20 veces.


Observe que eso seq 20 | xargs -Iz echo "Hi there z"generaría:

Hola 1
Hola 2
...

mitnk
fuente
44
ni siquiera necesitas el 1 (solo puedes correr seq 20)
Oleg Vaskevich
16
versión 2015:seq 20 | xargs -I{} echo "Hi there {}"
mitnk
44
Tenga en cuenta que zno es un buen marcador de posición porque es tan común que puede ser un texto normal en el texto del comando. El uso {}será mejor.
mitnk
44
¿Alguna forma de descartar el número seq? así que solo se imprimiría Hi there 20 veces (sin número)? EDITAR: solo use -I{}y luego no tenga ninguno {}en el comando
Mike Graf
1
Simplemente use algo, está seguro de que no estará en la salida, por ejemplo, IIIIIentonces se ve bien, por ejemplo:seq 20 | xargs -IIIIII timeout 10 yourCommandThatHangs
rubo77
79

Si está utilizando el shell zsh:

repeat 10 { echo 'Hello' }

Donde 10 es el número de veces que se repetirá el comando.

Wilson Silva
fuente
11
Si bien no es una respuesta a la pregunta (ya que menciona explícitamente bash), es muy útil y me ayudó a resolver mi problema. +1
OutOfBound
2
Además, si solo lo escribió en la terminal, podría usar algo comorepeat 10 { !! }
Renato Gomes el
28

Usando GNU Parallel puedes hacer:

parallel some_command ::: {1..1000}

Si no desea el número como argumento y solo ejecuta un trabajo a la vez:

parallel -j1 -N0 some_command ::: {1..1000}

Mire el video de introducción para una introducción rápida: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Recorra el tutorial ( http://www.gnu.org/software/parallel/parallel_tutorial.html ). Usted ordena línea con amor por ello.

Ole Tange
fuente
¿Por qué utilizar una herramienta cuya razón muy por la existencia, por ser manifiestamente demostrado por su nombre, es hacer las cosas de forma paralela , y luego darle argments crípticos -j1 -N0que a su vez fuera el paralelismo ????
Ross Presser
@RossPresser Esa es una pregunta muy razonable. La razón es que puede ser más fácil expresar lo que quiere hacer usando la sintaxis paralela de GNU. Piensa: ¿Cómo ejecutarías some_command1000 veces en serie sin argumentos? Y: ¿Puedes expresar eso más corto que parallel -j1 -N0 some_command ::: {1..1000}?
Ole Tange
Bueno, supongo que tienes una especie de punto, pero todavía se siente como usar un bloque de motor finamente diseñado como un martillo. Si estuviera suficientemente motivado y me sintiera considerado con mis futuros sucesores tratando de comprender lo que hice, probablemente envolvería la confusión en un script de shell y simplemente lo llamaría repeat.sh repeatcount some_command. Para la implementación en el script de shell que podría usar parallel, si ya estuviera instalado en la máquina (probablemente no lo estaría). O podría gravitar hacia xargs ya que parece ser más rápido cuando no se necesita redireccionamiento some_command.
Ross Presser
Pido disculpas si mi "pregunta muy razonable" se expresó de manera irrazonable.
Ross Presser
1
En este caso específico, puede no ser tan obvio. Se vuelve más obvio si usa más opciones de GNU Parallel, por ejemplo, si desea volver a intentar los comandos fallidos 4 veces y guardar la salida en diferentes archivos:parallel -N0 -j1 --retries 4 --results outputdir/ some_command
Ole Tange
18

Una función simple en el archivo de configuración bash (a ~/.bashrcmenudo) podría funcionar bien.

function runx() {
  for ((n=0;n<$1;n++))
    do ${*:2}
  done
}

Llámalo así.

$ runx 3 echo 'Hello world'
Hello world
Hello world
Hello world
acero
fuente
1
Gusta. Ahora, si pudiera cancelarse con ctrl + c, sería genial
slashdottir
¿Por qué usar esta forma de hacer eco de $ RANDOM n veces muestra el mismo número?
BladeMight
3
Esto funciona a la perfección. Lo único que necesitaba cambiar era que, en lugar de hacerlo do ${*:2}, agregué eval do eval ${*:2}para asegurarme de que funcionaría para ejecutar comandos que no comienzan con ejecutables. Por ejemplo, si desea establecer una variable de entorno antes de ejecutar un comando como SOME_ENV=test echo 'test'.
5_nd_5
12

xargses rápido :

#!/usr/bin/bash
echo "while loop:"
n=0; time while (( n++ < 10000 )); do /usr/bin/true ; done

echo -e "\nfor loop:"
time for ((n=0;n<10000;n++)); do /usr/bin/true ; done

echo -e "\nseq,xargs:"
time seq 10000 | xargs -I{} -P1 -n1 /usr/bin/true

echo -e "\nyes,xargs:"
time yes x | head -n10000 |  xargs -I{} -P1 -n1 /usr/bin/true

echo -e "\nparallel:"
time parallel --will-cite -j1 -N0 /usr/bin/true ::: {1..10000}

En un moderno Linux de 64 bits, da:

while loop:

real    0m2.282s
user    0m0.177s
sys     0m0.413s

for loop:

real    0m2.559s
user    0m0.393s
sys     0m0.500s

seq,xargs:

real    0m1.728s
user    0m0.013s
sys     0m0.217s

yes,xargs:

real    0m1.723s
user    0m0.013s
sys     0m0.223s

parallel:

real    0m26.271s
user    0m4.943s
sys     0m3.533s

Esto tiene sentido, ya que el xargscomando es un proceso nativo único que genera el /usr/bin/truecomando varias veces, en lugar de los bucles fory whileque se interpretan en Bash. Por supuesto, esto solo funciona para un solo comando; Si necesita hacer múltiples comandos en cada iteración del ciclo, será tan rápido, o tal vez más rápido, que pasar sh -c 'command1; command2; ...'a xargs

También -P1podría cambiarse, por ejemplo, -P8para generar 8 procesos en paralelo para obtener otro gran impulso en la velocidad.

No sé por qué GNU paralelo es tan lento. Pensé que sería comparable a los xargs.

James Scriven
fuente
1
GNU Parallel realiza una gran cantidad de configuración y contabilidad: admite cadenas de reemplazo programables, amortigua la salida para que la salida de dos trabajos paralelos nunca se mezcle, admite dirección (no puede hacer: xargs "echo> foo") y como no lo es escrito en bash tiene que generar un nuevo shell para hacer la redirección. La causa principal, sin embargo, está en el módulo Perl IPC :: open3, por lo que cualquier trabajo para lograrlo más rápido dará como resultado un paralelo de GNU más rápido.
Ole Tange
11

Otra forma de tu ejemplo:

n=0; while (( n++ < 10 )); do some_command; done
Pausado hasta nuevo aviso.
fuente
7

Por un lado, puede envolverlo en una función:

function manytimes {
    n=0
    times=$1
    shift
    while [[ $n -lt $times ]]; do
        $@
        n=$((n+1))
    done
}

Llámalo como:

$ manytimes 3 echo "test" | tr 'e' 'E'
tEst
tEst
tEst
bta
fuente
2
Esto no funcionará si el comando a ejecutar es más complejo que un comando simple sin redireccionamientos.
chepner
Puede hacer que funcione para casos más complejos, pero puede que tenga que ajustar el comando en una función o script.
bta
2
El traquí es engañoso y está trabajando en la salida de manytimes, no echo. Creo que deberías eliminarlo de la muestra.
Dmitry Ginzburg
7

xargs y seq ayudarán

function __run_times { seq 1 $1| { shift; xargs -i -- "$@"; } }

la vista :

abon@abon:~$ __run_times 3  echo hello world
hello world
hello world
hello world
firejox
fuente
2
¿Qué hace el cambio y el guión doble en el comando? ¿Es realmente necesario el 'cambio'?
sebs el
2
Esto no funcionará si el comando es más complejo que un comando simple sin redireccionamientos.
chepner
Muy lento, echo $ RANDOM 50 veces tomó 7 segundos, y aun así muestra el mismo número aleatorio en cada ejecución ...
BladeMight
6
for _ in {1..10}; do command; done   

Tenga en cuenta el guión bajo en lugar de usar una variable.

Ali Omary
fuente
99
Aunque técnicamente, _es un nombre variable.
gniourf_gniourf
5

Si está bien haciéndolo periódicamente, puede ejecutar el siguiente comando para ejecutarlo cada 1 segundo indefinidamente. Puede colocar otras comprobaciones personalizadas para ejecutarlo n varias veces.

watch -n 1 some_command

Si desea tener una confirmación visual de los cambios, agregue --differencesantes de lals comando.

Según la página del manual de OSX, también hay

La opción --cumulativa hace que el resaltado sea "pegajoso", presentando una visualización continua de todas las posiciones que alguna vez han cambiado. La opción -t o --no-title desactiva el encabezado que muestra el intervalo, el comando y la hora actual en la parte superior de la pantalla, así como la siguiente línea en blanco.

La página de manual de Linux / Unix se puede encontrar aquí

Pankaj Singhal
fuente
5

Resolví con este bucle, donde repetir es un número entero que representa el número del bucle.

repeat=10
for n in $(seq $repeat); 
    do
        command1
        command2
    done
fvlgnn
fuente
4

Otra respuesta más: use la expansión de parámetros en parámetros vacíos:

# calls curl 4 times 
curl -s -w "\n" -X GET "http:{,,,}//www.google.com"

Probado en Centos 7 y MacOS.

jcollum
fuente
Esto es interesante, ¿puede proporcionar más detalles sobre por qué funciona esto?
Hassan Mahmud
1
Está ejecutando la expansión del parámetro n veces, pero dado que el parámetro está vacío, en realidad no cambia lo que se ejecuta
jcollum
La búsqueda de expansión y curvatura de parámetros no ha dado resultados. Apenas sé rizo. Sería increíble si pudiera señalarme algún artículo u otro nombre común para esta función. Gracias.
Hassan Mahmud
1
Creo que esto puede ayudar gnu.org/software/bash/manual/html_node/…
jcollum
3

Parece que todas las respuestas existentes requieren bashy no funcionan con un BSD UNIX estándar /bin/sh(por ejemplo, kshen OpenBSD ).

El siguiente código debería funcionar en cualquier BSD:

$ echo {1..4}
{1..4}
$ seq 4
sh: seq: not found
$ for i in $(jot 4); do echo e$i; done
e1
e2
e3
e4
$
cnst
fuente
2

Un poco ingenuo, pero esto es lo que generalmente recuerdo de la cabeza:

for i in 1 2 3; do
  some commands
done

Muy similar a la respuesta de @ joe-koberg. La suya es mejor, especialmente si necesita muchas repeticiones, pero es más difícil para mí recordar otra sintaxis porque en los últimos años no uso bashmucho. No me refiero a las secuencias de comandos al menos.

akostadinov
fuente
1

El archivo de script

bash-3.2$ cat test.sh 
#!/bin/bash

echo "The argument is  arg: $1"

for ((n=0;n<$1;n++));
do
  echo "Hi"
done

y la salida a continuación

bash-3.2$  ./test.sh 3
The argument is  arg: 3
Hi
Hi
Hi
bash-3.2$
upog
fuente
0

Los bucles son probablemente la forma correcta de hacerlo, pero aquí hay una alternativa divertida:

echo -e {1..10}"\n" |xargs -n1 some_command

Si necesita el número de iteración como parámetro para su invocación, use:

echo -e {1..10}"\n" |xargs -I@ echo now I am running iteration @

Editar: se comentó correctamente que la solución dada anteriormente funcionaría sin problemas solo con ejecuciones de comandos simples (sin tuberías, etc.). siempre puedes usar a sh -cpara hacer cosas más complicadas, pero no vale la pena.

Otro método que uso típicamente es la siguiente función:

rep() { s=$1;shift;e=$1;shift; for x in `seq $s $e`; do c=${@//@/$x};sh -c "$c"; done;}

ahora puedes llamarlo como:

rep 3 10 echo iteration @

Los primeros dos números dan el rango. El @será traducido al número de iteración. Ahora también puedes usar esto con tuberías:

rep 1 10 "ls R@/|wc -l"

con darle el número de archivos en los directorios R1 .. R10.

Stacksia
fuente
1
Esto no funcionará si el comando es más complejo que un comando simple sin redireccionamientos.
chepner
bastante justo, pero mira mi edición arriba. El método editado utiliza un sh -c, por lo que también debería funcionar con la redirección.
stacksia
rep 1 2 touch "foo @"no creará dos archivos "foo 1" y "foo 2"; más bien crea tres archivos "foo", "1" y "2". Puede haber una manera de acertar con la cita usando printfy %q, pero será complicado obtener una secuencia arbitraria de palabras correctamente citadas en una sola cadena para pasar sh -c.
chepner
OP pidió más conciso , entonces, ¿por qué agrega algo como esto, cuando ya hay 5 respuestas más concisas?
zoska
Una vez que defina la definición de repen .bashrc, las invocaciones adicionales se vuelven mucho más concisas.
musiphil