Existe uma maneira melhor de executar um comando N vezes no bash?

304

Ocasionalmente, executo uma linha de comando do bash como esta:

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

Para correr some_command várias vezes seguidas - 10 vezes neste caso.

Frequentemente some_command é realmente uma cadeia de comandos ou um pipeline.

Existe uma maneira mais concisa de fazer isso?

bstpierre
fonte
1
Além das outras respostas legais, você pode usar em let ++nvez de n=$((n+1))(3 caracteres a menos).
Musiphil
1
Algumas indicações de quais desses métodos são portáteis seriam boas.
Faheem Mitha
serverfault.com/questions/273238/…
Ciro Santilli escreveu:
3
Se você está disposto a mudar conchas, zshtem repeat 10 do some_command; done.
chepner
1
@JohnVandivier Acabei de experimentar o bash em um novo contêiner do docker 18.04, a sintaxe original publicada na minha pergunta ainda funciona. Talvez você esteja executando um shell que não seja o bash, como / bin / sh, que é realmente traço; nesse caso, eu volto sh: 1: [[: not found.
precisa saber é

Respostas:

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

Ou como uma linha única para aqueles que desejam copiar e colar facilmente:

for run in {1..10}; do command; done
Joe Koberg
fonte
19
Se você tiver muitas iterações, o formulário com for (n=0;n<k;n++))pode ser melhor; Eu suspeito {1..k}que materialize uma string com todos os números inteiros separados por espaços.
Joe Koberg
@ Joe Koberg, obrigado pela dica. Normalmente, uso N <100, então isso parece bom.
precisa saber é o seguinte
10
@bstpierre: o formulário de expansão de chaves não pode usar variáveis ​​(facilmente) para especificar o intervalo no Bash.
Pausado até novo aviso.
5
Isso é verdade. A expansão de chaves é realizada antes da expansão de variáveis, de acordo com gnu.org/software/bash/manual/bashref.html#Brace-Expansion , portanto, nunca verá os valores de nenhuma variável.
Joe Koberg
5
Triste coisa sobre expansão variável. Eu estou usando o seguinte para fazer um loop n-times e ter números formatados: 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
user1830432
203

Usando uma constante:

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

Usando uma variável (pode incluir expressões matemáticas):

x=10; for ((n=0; n < (x / 2); n++)); do some_command; done
BatchyX
fonte
4
Esta é a maneira mais próxima da linguagem de programação ^^ - deve ser a aceita!
Nam G VU
Este é o melhor porque é uma única linha e, portanto, mais fácil de usar dentro do terminal
Kunok
Você também pode usar uma variável, por exemplo:x=10 for ((n=0; n < $x; n++));
Jelphy
@Kunok É igualmente aceitável executar a resposta aceita em uma linha:for run in {1..10}; do echo "on run $run"; done
Douglas Adams
e se njá estiver definido com um valor específico? for (( ; n<10; n++ ))não funciona / edit: melhor, provavelmente, utilizar uma das outras respostas como a while (( n++...um
phil294
121

Outra maneira simples de hackear:

seq 20 | xargs -Iz echo "Hi there"

executar eco 20 vezes.


Observe que a seq 20 | xargs -Iz echo "Hi there z"saída seria:

Olá 1
Olá 2
...

mitnk
fonte
4
você não precisa mesmo de a 1 (pode apenas correr seq 20)
Oleg Vaskevich
16
versão 2015:seq 20 | xargs -I{} echo "Hi there {}"
mitnk 12/05
4
Observe que znão é um bom espaço reservado, porque é tão comum que pode ser um texto comum no texto de comando. O uso {}será melhor.
Mitnk 28/10/2015
4
Alguma maneira de descartar o número seq? então seria só imprimir Hi there 20 vezes (sem número)? EDIT: basta usar -I{}e, em seguida, não tem nenhum {}comando
Mike Graf
1
Basta usar alguma coisa, você tem certeza que não vai ser na saída, por exemplo IIIII, então ele parece bom, por exemplo:seq 20 | xargs -IIIIII timeout 10 yourCommandThatHangs
rubo77
79

Se você estiver usando o shell zsh:

repeat 10 { echo 'Hello' }

Onde 10 é o número de vezes que o comando será repetido.

Wilson Silva
fonte
11
Embora não seja uma resposta para a pergunta (uma vez que menciona explicitamente o bash), é muito útil e me ajudou a resolver meu problema. 1
OutOfBound
2
Além disso, se você o digitasse no terminal, poderia usar algo parecido com:repeat 10 { !! }
Renato Gomes
28

Usando o GNU Parallel, você pode:

parallel some_command ::: {1..1000}

Se você não quiser o número como argumento e executar apenas um único trabalho por vez:

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

Assista ao vídeo de introdução para uma rápida introdução: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Percorra o tutorial ( http://www.gnu.org/software/parallel/parallel_tutorial.html ). Você comanda a linha com amor por isso.

Ole Tange
fonte
Por que você usaria uma ferramenta cuja razão de existência, como manifestamente comprovada por seu nome, é executar as coisas em paralelo e fornecer argumentos criptográficos -j1 -N0que desativam o paralelismo ????
Ross Presser
@ RossPresser Essa é uma pergunta muito razoável. O motivo é que pode ser mais fácil expressar o que você deseja fazer usando a sintaxe GNU Parallel. Pense: como você executaria some_command1000 vezes em série sem argumentos? E: você pode expressar isso mais curto que parallel -j1 -N0 some_command ::: {1..1000}?
Ole Tange
Bem, acho que você tem uma certa razão, mas ainda parece que está usando um bloco de motor finamente trabalhado como martelo. Se eu estivesse suficientemente motivado e considerasse meus futuros sucessores tentando entender o que eu fazia, provavelmente embrulharia a confusão em um script de shell e simplesmente o chamaria repeat.sh repeatcount some_command. Para implementação no shell script, eu poderia usar parallel, se já estivesse instalado na máquina (provavelmente não seria). Ou posso gravitar em direção ao xargs, já que isso parece ser mais rápido quando nenhum redirecionamento é necessário some_command.
Ross Presser 28/02
Peço desculpas se minha "pergunta muito razoável" foi expressa de maneira irracional.
Ross Presser
1
Nesse caso específico, pode não ser tão óbvio. Torna-se mais óbvio se você usar mais opções do GNU Parallel, por exemplo, se desejar tentar novamente os comandos com falha 4 vezes e salvar a saída em arquivos diferentes:parallel -N0 -j1 --retries 4 --results outputdir/ some_command
Ole Tange
18

Uma função simples no arquivo de configuração do bash ( ~/.bashrcgeralmente) pode funcionar bem.

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

Chame assim.

$ runx 3 echo 'Hello world'
Hello world
Hello world
Hello world
aço
fonte
1
Gosto disso. Agora, se pudesse ser cancelado com ctrl + c, seria ótimo
slashdottir
Por que usar essa maneira de ecoar $ RANDOM n vezes exibe o mesmo número?
BladeMight
3
Isso funciona perfeitamente. A única coisa que eu precisava mudar era, em vez de apenas fazer do ${*:2}, adicionei eval do eval ${*:2}para garantir que funcionasse para executar comandos que não iniciam com executáveis. Por exemplo, se você deseja definir uma variável de ambiente antes de executar um comando como SOME_ENV=test echo 'test'.
5_nd_5
12

xargsé 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}

Em um Linux moderno de 64 bits, fornece:

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

Isso faz sentido, pois o xargscomando é um único processo nativo que gera o /usr/bin/truecomando várias vezes, em vez dos loops fore whileque são interpretados no Bash. Claro que isso só funciona para um único comando; se você precisar executar vários comandos em cada iteração do loop, será tão rápido, ou talvez mais rápido, do que passarsh -c 'command1; command2; ...' para xargs

Também -P1pode ser alterado para, digamos,-P8 gerar 8 processos em paralelo para obter outro grande aumento de velocidade.

Não sei por que o GNU paralelo é tão lento. Eu teria pensado que seria comparável ao xargs.

James Scriven
fonte
1
O GNU Parallel faz muitas configurações e contabilidade: ele suporta seqüências de substituição programáveis, armazena em buffer a saída para que a saída de dois trabalhos paralelos nunca seja misturada, suporta direção (você não pode fazer: xargs "echo> foo") e como não é escrito no bash, ele deve gerar um novo shell para fazer o redirecionamento. A causa principal, no entanto, está no módulo Perl IPC :: open3 - portanto, qualquer trabalho para obter isso mais rápido resultará em um GNU Parallel mais rápido.
precisa
11

Outra forma do seu exemplo:

n=0; while (( n++ < 10 )); do some_command; done
Pausado até novo aviso.
fonte
7

Por um lado, você pode agrupá-lo em uma função:

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

Chame assim:

$ manytimes 3 echo "test" | tr 'e' 'E'
tEst
tEst
tEst
bta
fonte
2
Isso não funcionará se o comando a ser executado for mais complexo do que um comando simples, sem redirecionamentos.
chepner
Você pode fazê-lo funcionar para casos mais complexos, mas pode ser necessário agrupar o comando em uma função ou script.
BTA
2
O traqui é enganador e está trabalhando na saída de manytimes, não echo. Eu acho que você deve removê-lo da amostra.
Dmitry Ginzburg
7

xargs e seq ajudarão

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

a vista :

abon@abon:~$ __run_times 3  echo hello world
hello world
hello world
hello world
firejox
fonte
2
o que a mudança e o traço duplo no comando fazem? A 'mudança' é realmente necessária?
SEBS
2
Isso não funcionará se o comando for mais complexo do que um comando simples, sem redirecionamentos.
chepner
Muito lento, echo $ RANDOM 50 vezes levou 7 seg, e mesmo assim ele exibe mesmo número aleatório cada corrida ...
BladeMight
6
for _ in {1..10}; do command; done   

Observe o sublinhado em vez de usar uma variável.

Ali Omary
fonte
9
Embora tecnicamente, _é um nome de variável.
gniourf_gniourf
5

Se você fizer isso periodicamente, poderá executar o seguinte comando para executá-lo a cada 1 segundo indefinidamente. Você pode colocar outras verificações personalizadas para executá-lo n vezes.

watch -n 1 some_command

Se você deseja ter confirmação visual das alterações, acrescente --differencesantes do lscomando.

De acordo com a página do manual OSX, também há

A opção --cumulative torna o destaque "pegajoso", apresentando uma exibição contínua de todas as posições que já foram alteradas. A opção -t ou --no-title desativa o cabeçalho, mostrando o intervalo, o comando e a hora atual na parte superior da tela, bem como a seguinte linha em branco.

A página do manual Linux / Unix pode ser encontrada aqui

Pankaj Singhal
fonte
5

Eu resolvi com esse loop, onde repeat é um número inteiro que representa o número do loop

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

Outra resposta: use a expansão de parâmetros em parâmetros vazios:

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

Testado em Centos 7 e MacOS.

jcollum
fonte
Isso é interessante, você pode fornecer mais alguns detalhes sobre por que isso funciona?
Hassan Mahmud
1
Está funcionando a expansão parâmetro n vezes, mas uma vez que o parâmetro é esvaziá-la na verdade não mudar o que é executado
jcollum
A pesquisa de expansão e curvatura de parâmetros não resultou em resultados. Eu mal sei enrolar. Seria incrível se você pudesse me indicar um artigo ou outro nome comum para esse recurso. Obrigado.
Hassan Mahmud
1
Eu acho que isso pode ajudar gnu.org/software/bash/manual/html_node/…
jcollum
3

Todas as respostas existentes parecem exigir bashe não funcionam com um BSD UNIX padrão /bin/sh(por exemplo, kshno OpenBSD ).

O código abaixo deve funcionar em qualquer 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
fonte
2

Um pouco ingênuo, mas é disso que geralmente me lembro de cima:

for i in 1 2 3; do
  some commands
done

Muito semelhante à resposta de @ joe-koberg. O dele é melhor, especialmente se você precisar de muitas repetições, apenas é mais difícil me lembrar de outra sintaxe, porque nos últimos anos não estou usando bashmuito. Quero dizer, não pelo menos para scripts.

akostadinov
fonte
1

O arquivo 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

e a saída abaixo

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

E a forma alternativa formencionada em (bashref) Construções em loop ?

SamB
fonte
4
jogá-lo um osso e dar um exemplo da forma alternativa?
Joe Koberg
0

Pois loops são provavelmente o caminho certo para fazê-lo, mas aqui está uma alternativa divertida:

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

Se você precisar do número da iteração como parâmetro para sua chamada, use:

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

Edit: Foi comentado corretamente que a solução fornecida acima funcionaria sem problemas apenas com execuções simples de comando (sem pipes, etc.). você sempre pode usar umsh -c para fazer coisas mais complicadas, mas não vale a pena.

Outro método usado normalmente é a seguinte função:

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

agora você pode chamá-lo como:

rep 3 10 echo iteration @

Os dois primeiros números indicam o intervalo. O @será traduzido para o número da iteração. Agora você também pode usar isso com tubos:

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

com o número de arquivos nos diretórios R1 .. R10.

stacksia
fonte
1
Isso não funcionará se o comando for mais complexo do que um comando simples, sem redirecionamentos.
chepner
justo o suficiente, mas veja minha edição acima. O método editado usa um sh -c, portanto, também deve funcionar com o redirecionamento.
stacksia
rep 1 2 touch "foo @"não criará dois arquivos "foo 1" e "foo 2"; em vez disso, cria três arquivos "foo", "1" e "2". Pode haver uma maneira de fazer a citação correta usando printfe %q, mas será complicado obter uma sequência arbitrária de palavras corretamente citadas em uma única sequência para a qual passar sh -c.
chepner
O OP pediu uma informação mais concisa . Por que você está adicionando algo assim, quando já existem mais 5 respostas concisas?
zoska
Depois de definir a definição de repno seu .bashrc, outras invocações se tornam muito mais concisas.
musiphil