C'è un modo migliore per eseguire un comando N volte in bash?


304

Occasionalmente eseguo una riga di comando bash come questa:

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

Per eseguire some_commandun numero di volte consecutive - 10 volte in questo caso.

Spesso some_commandè davvero una catena di comandi o una pipeline.

C'è un modo più conciso per farlo?


1
Oltre alle altre belle risposte, puoi usare let ++ninvece di n=$((n+1))(3 caratteri in meno).
musiphil,

1
Alcune indicazioni di quali di questi metodi sono portatili sarebbero utili.
Faheem Mitha,


3
Se sei disposto a cambiare shell, zshè repeat 10 do some_command; done.
Chepner,

1
@JohnVandivier L'ho appena provato a Bash in un nuovo contenitore docker 18.04, la sintassi originale pubblicata nella mia domanda funziona ancora. Forse stai eseguendo una shell diversa da bash, come / bin / sh, che è davvero un trattino, nel qual caso torno indietro sh: 1: [[: not found.
bstpierre,

Risposte:


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

O come one-liner per coloro che vogliono copiare e incollare facilmente:

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

19
Se hai MOLTE iterazioni, il modulo con for (n=0;n<k;n++))potrebbe essere migliore; Sospetto {1..k}che materializzerà una stringa con tutti quegli interi separati da spazi.
Joe Koberg,

@Joe Koberg, grazie per la punta. In genere sto usando N <100 quindi questo sembra buono.
bstpierre,

10
@bstpierre: il modulo di espansione del controvento non può usare (facilmente) le variabili per specificare l'intervallo in Bash.
In pausa fino a ulteriore avviso.

5
Questo è vero. L'espansione delle parentesi graffe viene eseguita prima dell'espansione delle variabili in base a gnu.org/software/bash/manual/bashref.html#Brace-Expansion , quindi non vedrà mai i valori di nessuna variabile.
Joe Koberg,

5
Cosa triste per l'espansione variabile. Sto usando il seguente per 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
ripetere

203

Utilizzando una costante:

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

Utilizzando una variabile (può includere espressioni matematiche):

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

4
Questo è il modo più vicino al linguaggio di programmazione ^^ - dovrebbe essere quello accettato!
Nam G VU,

Questo è meglio perché è una linea singola e quindi più facile da usare all'interno del terminal
Kunok

Puoi anche usare una variabile, ad esempiox=10 for ((n=0; n < $x; n++));
Jelphy

@Kunok È altrettanto accettabile eseguire la risposta accettata in una riga:for run in {1..10}; do echo "on run $run"; done
Douglas Adams,

cosa succede se nè già impostato su un valore specifico? for (( ; n<10; n++ ))non funziona / modifica: probabilmente è meglio usare una delle altre risposte come while (( n++...quella
phil294

121

Un altro modo semplice per hackerarlo:

seq 20 | xargs -Iz echo "Hi there"

esegui l'eco 20 volte.


Si noti che seq 20 | xargs -Iz echo "Hi there z"produrrebbe:

Ciao a tutti 1
Ciao a tutti 2
...


4
non hai nemmeno bisogno del 1 (puoi semplicemente correre seq 20)
Oleg Vaskevich,

16
versione 2015:seq 20 | xargs -I{} echo "Hi there {}"
mitnk,

4
Nota che znon è un buon segnaposto perché è così comune che può essere un testo normale nel testo del comando. L'uso {}sarà migliore.
mitnk,

4
In che modo scartare il numero seq? quindi verrebbe stampato Hi there 20 volte (nessun numero)? EDIT: basta usare -I{}e quindi non avere alcun {}comando
Mike Graf

1
Basta usare qualcosa, sei sicuro che non sarà nell'output, ad esempio, IIIIIallora sembra carino per esempio:seq 20 | xargs -IIIIII timeout 10 yourCommandThatHangs
rubo77,

79

Se stai usando la shell zsh:

repeat 10 { echo 'Hello' }

Dove 10 è il numero di volte che il comando verrà ripetuto.


11
Sebbene non sia una risposta alla domanda (poiché menziona esplicitamente bash), è molto utile e mi ha aiutato a risolvere il mio problema. +1
OutOfBound,

2
Inoltre, se lo hai appena digitato nel terminale, potresti usare qualcosa di similerepeat 10 { !! }
Renato Gomes,

28

Usando GNU Parallel puoi fare:

parallel some_command ::: {1..1000}

Se non si desidera il numero come argomento ed eseguire solo un singolo lavoro alla volta:

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

Guarda il video introduttivo per una rapida introduzione: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Segui il tutorial ( http://www.gnu.org/software/parallel/parallel_tutorial.html ). La riga di comando ti ama per questo.


Perché dovresti usare uno strumento la cui vera ragione di esistenza, come manifestamente dimostrato dal suo nome, è far funzionare le cose in parallelo , e quindi dargli argomenti criptici -j1 -N0che disattivano il parallelismo ????
Ross Presser,

@RossPresser Questa è una domanda molto ragionevole. Il motivo è che potrebbe essere più semplice esprimere ciò che si desidera fare usando la sintassi GNU Parallel. Pensa: come eseguiresti some_command1000 volte in serie senza argomenti? E: puoi esprimere quello più breve di parallel -j1 -N0 some_command ::: {1..1000}?
Ole Tange,

Beh, immagino tu abbia una specie di punto, ma sembra davvero di usare un blocco motore finemente realizzato come un martello. Se fossi sufficientemente motivato, e mi sentissi premuroso dei miei futuri successori che cercavano di capire cosa facessi, probabilmente avvolgerei la confusione in uno script di shell e lo chiamerei semplicemente repeat.sh repeatcount some_command. Per l'implementazione nello script della shell potrei usare parallel, se fosse già installato sulla macchina (probabilmente non lo sarebbe). Oppure potrei gravitare verso gli xarg, dato che sembra essere il più veloce quando non è necessario alcun reindirizzamento some_command.
Ross Presser,

Mi scuso se la mia "domanda molto ragionevole" è stata espressa in modo irragionevole.
Ross Presser,

1
In questo caso specifico potrebbe non essere così ovvio. Diventa più ovvio se usi più opzioni di GNU Parallel, ad esempio se vuoi riprovare 4 volte a comandi falliti e salvare l'output in file diversi:parallel -N0 -j1 --retries 4 --results outputdir/ some_command
Ole Tange,

18

Una semplice funzione nel file di configurazione di bash ( ~/.bashrcspesso) potrebbe funzionare bene.

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

Chiamalo così.

$ runx 3 echo 'Hello world'
Hello world
Hello world
Hello world

1
Mi piace. Ora, se potesse essere cancellato con ctrl + c, sarebbe fantastico
slashdottir,

Perché usare questo modo per ripetere $ RANDOM n volte visualizza lo stesso numero?
BladeMight,

3
Funziona perfettamente. L'unica cosa che dovevo cambiare era invece di do ${*:2}aggiungere semplicemente eval do eval ${*:2}per assicurarmi che funzionasse per l'esecuzione di comandi che non iniziano con gli eseguibili. Ad esempio, se si desidera impostare una variabile di ambiente prima di eseguire un comando come SOME_ENV=test echo 'test'.
5_nd_5,

12

xargsè veloce :

#!/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}

Su un moderno Linux a 64 bit, offre:

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

Questo ha senso, poiché il xargscomando è un singolo processo nativo che genera il /usr/bin/truecomando più volte, invece dei cicli fore whileche sono tutti interpretati in Bash. Naturalmente questo funziona solo per un singolo comando; se devi eseguire più comandi in ogni iterazione del ciclo, sarà altrettanto veloce, o forse più veloce, del passaggiosh -c 'command1; command2; ...' a xargs

L' -P1potrebbe anche essere modificato, per esempio,-P8 a deporre le uova 8 processi in parallelo per ottenere un altro grande spinta in termini di velocità.

Non so perché il parallelo GNU sia così lento. Avrei pensato che sarebbe paragonabile a Xargs.


1
GNU Parallel esegue parecchie impostazioni e contabilità: supporta stringhe di sostituzione programmabili, bufferizza l'output in modo che l'output di due lavori paralleli non sia mai mischiato, supporta la direzione (non è possibile eseguire: xargs "echo> foo") e poiché non lo è scritto in bash deve generare una nuova shell per eseguire il reindirizzamento. La causa principale, tuttavia, è nel modulo Perl IPC :: open3 - quindi qualsiasi lavoro per ottenere quello più veloce si tradurrà in un GNU Parallel più veloce.
Ole Tange,


7

Per uno, puoi avvolgerlo in una funzione:

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

Chiamalo come:

$ manytimes 3 echo "test" | tr 'e' 'E'
tEst
tEst
tEst

2
Questo non funzionerà se il comando da eseguire è più complesso di un semplice comando senza reindirizzamenti.
Chepner,

Puoi farlo funzionare per casi più complessi, ma potresti dover avvolgere il comando in una funzione o in uno script.
BTA

2
Il trqui è fuorviante e funziona sull'output di manytimes, no echo. Penso che dovresti rimuoverlo dal campione.
Dmitry Ginzburg,

7

xargs e seq aiuteranno

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

2
cosa fanno il turno e il doppio trattino nel comando? Il "turno" è davvero necessario?
Sebs

2
Questo non funzionerà se il comando è più complesso di un semplice comando senza reindirizzamenti.
Chepner,

Molto lento, l'eco $ RANDOM 50 volte ha richiesto 7 secondi, e anche così mostra lo stesso numero casuale ad ogni corsa ...
BladeMight

6
for _ in {1..10}; do command; done   

Nota il carattere di sottolineatura invece di usare una variabile.


9
Sebbene tecnicamente, _è un nome variabile.
gniourf_gniourf,

5

Se stai bene eseguendolo periodicamente, puoi eseguire il comando seguente per eseguirlo ogni 1 secondo a tempo indeterminato. Puoi eseguire altri controlli personalizzati per eseguirlo n numero di volte.

watch -n 1 some_command

Se si desidera avere una conferma visiva delle modifiche, aggiungere --differencesprima dils comando.

Secondo la pagina man di OSX, c'è anche

L'opzione --cumulative rende l'evidenziazione "appiccicosa", presentando una visualizzazione corrente di tutte le posizioni che sono mai cambiate. L'opzione -t o --no-title disattiva l'intestazione che mostra l'intervallo, il comando e l'ora corrente nella parte superiore del display, nonché la seguente riga vuota.

La pagina man di Linux / Unix può essere trovata qui


5

Ho risolto con questo loop, in cui repeat è un numero intero che rappresenta il numero dei loop

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

4

Ancora un'altra risposta: utilizzare l'espansione dei parametri su parametri vuoti:

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

Testato su Centos 7 e MacOS.


Questo è interessante, puoi fornire qualche dettaglio in più sul perché funziona?
Hassan Mahmud,

1
Sta eseguendo l'espansione del parametro n volte ma poiché il parametro è vuoto non cambia effettivamente ciò che viene eseguito
jcollum

La ricerca di espansione e arricciatura dei parametri non ha prodotto risultati. Conosco a malapena il ricciolo. Sarebbe fantastico se potessi indicarmi un articolo o forse un altro nome comune per questa funzione. Grazie.
Hassan Mahmud,

1
Penso che questo possa aiutare gnu.org/software/bash/manual/html_node/…
jcollum

3

Tutte le risposte esistenti sembrano richiedere bashe non funzionano con un BSD UNIX standard /bin/sh(ad es. kshSu OpenBSD ).

Il codice seguente dovrebbe funzionare su qualsiasi 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
$

2

Un po 'ingenuo ma questo è ciò che di solito ricordo dalla cima della mia testa:

for i in 1 2 3; do
  some commands
done

Molto simile alla risposta di @ joe-koberg. È meglio soprattutto se hai bisogno di molte ripetizioni, solo più difficile per me ricordare altre sintassi perché negli ultimi anni non sto usando bashmolto. Intendo non per lo scripting almeno.


1

Il file di 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 l'uscita di seguito

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


0

I loop sono probabilmente il modo giusto per farlo, ma ecco una divertente alternativa:

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

Se è necessario il numero di iterazione come parametro per la propria chiamata, utilizzare:

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

Modifica: è stato giustamente commentato che la soluzione fornita sopra funzionerebbe senza problemi solo con semplici esecuzioni di comandi (senza pipe, ecc.). puoi sempre usare ash -c per fare cose più complicate, ma non ne vale la pena.

Un altro metodo che uso in genere è la seguente funzione:

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

ora puoi chiamarlo come:

rep 3 10 echo iteration @

I primi due numeri indicano l'intervallo. Il @otterrà tradotta al numero di iterazioni. Ora puoi usarlo anche con le pipe:

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

con ti danno il numero di file nelle directory R1 .. R10.


1
Questo non funzionerà se il comando è più complesso di un semplice comando senza reindirizzamenti.
Chepner,

abbastanza giusto, ma vedi la mia modifica sopra. Il metodo modificato utilizza un sh -c, quindi dovrebbe funzionare anche con il reindirizzamento.
Stacksia,

rep 1 2 touch "foo @"non creerà due file "pippo 1" e "pippo 2"; piuttosto crea tre file "pippo", "1" e "2". Potrebbe esserci un modo per ottenere la citazione corretta usando printfe %q, ma sarà difficile ottenere una sequenza arbitraria di parole correttamente citate in una singola stringa a cui passare sh -c.
Chepner,

OP ha chiesto più conciso , quindi perché stai aggiungendo qualcosa di simile, quando ci sono già 5 risposte più concise?
zoska,

Una volta definita la definizione repin .bashrc, ulteriori invocazioni diventano molto più concise.
musiphil,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.