Gibt es eine bessere Möglichkeit, einen Befehl N-mal in Bash auszuführen?

304

Ich führe gelegentlich eine Bash-Befehlszeile wie folgt aus:

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

some_commandMehrmals hintereinander ausführen - in diesem Fall zehnmal.

Oft some_commandist es wirklich eine Befehlskette oder eine Pipeline.

Gibt es eine präzisere Möglichkeit, dies zu tun?

bstpierre
quelle
1
Neben den anderen netten Antworten können Sie let ++nanstelle von n=$((n+1))(3 Zeichen weniger) verwenden.
Musiphil
1
Einige Hinweise darauf, welche dieser Methoden portabel sind, wären hilfreich.
Faheem Mitha
serverfault.com/questions/273238/…
Ciro Santilli 法轮功 冠状 冠状 六四 六四 法轮功
3
Wenn Sie bereit sind, Muscheln zu wechseln, zshhat repeat 10 do some_command; done.
Chepner
1
@ JohnVandivier Ich habe es gerade in Bash in einem neuen Docker-Container vom 18.04 versucht. Die ursprüngliche Syntax, wie in meiner Frage angegeben, funktioniert immer noch. Vielleicht führen Sie eine andere Shell als bash aus, wie / bin / sh, was wirklich Dash ist. In diesem Fall komme ich zurück sh: 1: [[: not found.
Bstpierre

Antworten:

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

Oder als Einzeiler für diejenigen, die einfach kopieren und einfügen möchten:

for run in {1..10}; do command; done
Joe Koberg
quelle
19
Wenn Sie viele Iterationen haben, ist das Formular mit for (n=0;n<k;n++))möglicherweise besser. Ich vermute, {1..k}dass eine Zeichenfolge mit all diesen durch Leerzeichen getrennten Ganzzahlen materialisiert wird.
Joe Koberg
@ Joe Koberg, danke für den Tipp. Ich benutze normalerweise N <100, also scheint das gut zu sein.
Bstpierre
10
@bstpierre: Das Klammererweiterungsformular kann keine Variablen (einfach) verwenden, um den Bereich in Bash anzugeben.
Bis auf weiteres angehalten.
5
Das ist wahr. Die Klammererweiterung wird vor der Variablenerweiterung gemäß gnu.org/software/bash/manual/bashref.html#Brace-Expansion durchgeführt , sodass niemals die Werte von Variablen angezeigt werden.
Joe Koberg
5
Traurige Sache über variable Expansion. Ich benutze folgendes, um n-mal zu schleifen und habe Zahlen 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
formatiert
203

Verwenden einer Konstante:

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

Verwenden einer Variablen (kann mathematische Ausdrücke enthalten):

x=10; for ((n=0; n < (x / 2); n++)); do some_command; done
BatchyX
quelle
4
Dies ist der Programmiersprache am nächsten ^^ - sollte der akzeptierte sein!
Nam G VU
Dieser ist besser, weil es eine einzelne Leitung ist und daher einfacher im Terminal zu verwenden ist
Kunok
Sie können auch eine Variable verwenden, z. B.x=10 for ((n=0; n < $x; n++));
Jelphy
@ Kunok Es ist genauso akzeptabel, die akzeptierte Antwort in einer Zeile auszuführen:for run in {1..10}; do echo "on run $run"; done
Douglas Adams
Was ist, wenn nbereits ein bestimmter Wert festgelegt ist? for (( ; n<10; n++ ))funktioniert nicht / bearbeitet: Verwenden Sie am besten eine der anderen Antworten wie die while (( n++...eine
phil294
121

Ein weiterer einfacher Weg, um es zu hacken:

seq 20 | xargs -Iz echo "Hi there"

Echo 20 Mal ausführen.


Beachten Sie, dass seq 20 | xargs -Iz echo "Hi there z"Folgendes ausgegeben werden würde:

Hallo 1
Hallo 2
...

mitnk
quelle
4
Sie brauchen nicht einmal die 1 (kann nur laufen seq 20)
Oleg Vaskevich
16
Version 2015:seq 20 | xargs -I{} echo "Hi there {}"
Mitnk
4
Beachten Sie, dass dies zkein guter Platzhalter ist, da er so häufig ein normaler Text im Befehlstext sein kann. Verwendung {}wird besser sein.
Mitnk
4
Gibt es eine Möglichkeit, die Nummer von zu verwerfen seq? also würde es nur Hi there 20 mal drucken (keine nummer)? BEARBEITEN: einfach verwenden -I{}und dann keine {}im Befehl haben
Mike Graf
1
Verwenden Sie einfach etwas, Sie sind sicher, dass es nicht in der Ausgabe sein wird, zum Beispiel IIIIIsieht es zum Beispiel gut aus:seq 20 | xargs -IIIIII timeout 10 yourCommandThatHangs
rubo77
79

Wenn Sie die zsh-Shell verwenden:

repeat 10 { echo 'Hello' }

Dabei ist 10 die Häufigkeit, mit der der Befehl wiederholt wird.

Wilson Silva
quelle
11
Es ist zwar keine Antwort auf die Frage (da Bash ausdrücklich erwähnt wird), aber es ist sehr nützlich und hat mir geholfen, mein Problem zu lösen. +1
OutOfBound
2
Wenn Sie es nur im Terminal eingegeben haben, können Sie auch Folgendes verwendenrepeat 10 { !! }
Renato Gomes
28

Mit GNU Parallel können Sie Folgendes tun:

parallel some_command ::: {1..1000}

Wenn Sie die Nummer nicht als Argument verwenden und jeweils nur einen Job ausführen möchten:

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

Sehen Sie sich das Intro-Video für eine kurze Einführung an: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Gehen Sie durch das Tutorial ( http://www.gnu.org/software/parallel/parallel_tutorial.html ). Sie Kommandozeile mit Liebe Sie dafür.

Ole Tange
quelle
Warum würden Sie ein Werkzeug , dessen sehr Grund für die Existenz verwenden, als offensichtlich durch seinen Namen bewiesen, ist , die Dinge zu laufen parallel , und dann geben wir es kryptisch argments -j1 -N0dass wiederum von der Parallelität ????
Ross Presser
@ RossPresser Das ist eine sehr vernünftige Frage. Der Grund dafür ist, dass es möglicherweise einfacher ist, mit der GNU Parallel-Syntax auszudrücken, was Sie tun möchten. Denken Sie: Wie würden Sie some_command1000 Mal in Serie ohne Argumente laufen ? Und: Kannst du das kürzer ausdrücken als parallel -j1 -N0 some_command ::: {1..1000}?
Ole Tange
Nun, ich denke, Sie haben eine Art Punkt, aber es fühlt sich immer noch so an, als würde man einen fein gearbeiteten Motorblock als Hammer verwenden. Wenn ich ausreichend motiviert wäre und Rücksicht auf meine zukünftigen Nachfolger nehmen würde, die versuchen zu verstehen, was ich getan habe, würde ich die Verwirrung wahrscheinlich in ein Shell-Skript einwickeln und es einfach mit nennen repeat.sh repeatcount some_command. Für die Implementierung im Shell-Skript könnte ich verwenden parallel, wenn es bereits auf dem Computer installiert war (wahrscheinlich nicht). Oder ich mag Xargs, da dies am schnellsten zu sein scheint, wenn keine Umleitung von erforderlich ist some_command.
Ross Presser
Ich entschuldige mich, wenn meine "sehr vernünftige Frage" auf unangemessene Weise ausgedrückt wurde.
Ross Presser
1
In diesem speziellen Fall ist dies möglicherweise nicht ganz so offensichtlich. Es wird offensichtlicher, wenn Sie mehr Optionen von GNU Parallel verwenden, z. B. wenn Sie fehlgeschlagene Befehle viermal wiederholen und die Ausgabe in verschiedenen Dateien speichern möchten:parallel -N0 -j1 --retries 4 --results outputdir/ some_command
Ole Tange
18

Eine einfache Funktion in der Bash-Konfigurationsdatei ( ~/.bashrcoft) könnte gut funktionieren.

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

Nennen Sie es so.

$ runx 3 echo 'Hello world'
Hello world
Hello world
Hello world
Stahl
quelle
1
Mag ich. Wenn es nun mit Strg + C abgebrochen werden könnte, wäre das großartig
slashdottir
Warum wird bei Verwendung dieser Methode zum n-maligen Echo von $ RANDOM dieselbe Nummer angezeigt?
BladeMight
3
Das funktioniert perfekt. Das einzige, was ich ändern musste, war, anstatt nur zu tun, do ${*:2}eval hinzuzufügen do eval ${*:2}, um sicherzustellen, dass es zum Ausführen von Befehlen funktioniert, die nicht mit ausführbaren Dateien beginnen. Zum Beispiel, wenn Sie eine Umgebungsvariable festlegen möchten, bevor Sie einen Befehl wie ausführen SOME_ENV=test echo 'test'.
5_nd_5
12

xargsist schnell :

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

Auf einem modernen 64-Bit-Linux gibt es:

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

Dies ist sinnvoll, da der xargsBefehl ein einzelner nativer Prozess ist, der den /usr/bin/trueBefehl mehrmals erzeugt, anstelle der forund- whileSchleifen, die alle in Bash interpretiert werden. Dies funktioniert natürlich nur für einen einzelnen Befehl. Wenn Sie in jeder Iteration der Schleife mehrere Befehle ausführen müssen, ist diese genauso schnell oder möglicherweise schneller als die Übergabe sh -c 'command1; command2; ...'an xargs

Das -P1könnte auch geändert werden, -P8um beispielsweise 8 Prozesse parallel zu erzeugen, um einen weiteren großen Geschwindigkeitsschub zu erzielen.

Ich weiß nicht, warum GNU parallel so langsam ist. Ich hätte gedacht, es wäre vergleichbar mit Xargs.

James Scriven
quelle
1
GNU Parallel erledigt eine Menge Setup- und Buchhaltungsarbeiten: Es unterstützt programmierbare Ersatzzeichenfolgen, puffert die Ausgabe, sodass die Ausgabe von zwei parallelen Jobs niemals gemischt wird, unterstützt die Richtung (Sie können dies nicht tun: xargs "echo> foo") und da dies nicht der Fall ist geschrieben in bash muss es eine neue Shell erzeugen, um die Umleitung durchzuführen. Die Hauptursache liegt jedoch im Perl-Modul IPC :: open3. Wenn Sie also daran arbeiten, dies schneller zu erreichen, wird GNU Parallel schneller.
Ole Tange
11

Eine andere Form Ihres Beispiels:

n=0; while (( n++ < 10 )); do some_command; done
Bis auf weiteres angehalten.
quelle
7

Zum einen können Sie es in eine Funktion einschließen:

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

Nennen Sie es wie:

$ manytimes 3 echo "test" | tr 'e' 'E'
tEst
tEst
tEst
bta
quelle
2
Dies funktioniert nicht, wenn der auszuführende Befehl komplexer ist als ein einfacher Befehl ohne Umleitungen.
Chepper
Sie können es für komplexere Fälle verwenden, müssen den Befehl jedoch möglicherweise in eine Funktion oder ein Skript einschließen.
bta
2
Das trhier ist irreführend und es arbeitet an der Ausgabe von manytimes, nicht echo. Ich denke, Sie sollten es aus der Probe entfernen.
Dmitry Ginzburg
7

xargs und seq werden helfen

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

die Aussicht :

abon@abon:~$ __run_times 3  echo hello world
hello world
hello world
hello world
firejox
quelle
2
Was bewirken die Verschiebung und der Doppelstrich im Befehl? Ist die "Verschiebung" wirklich notwendig?
Sebs
2
Dies funktioniert nicht, wenn der Befehl komplexer ist als ein einfacher Befehl ohne Umleitungen.
Chepper
Sehr langsam, Echo $ RANDOM 50 Mal dauerte 7 Sekunden, und
trotzdem
6
for _ in {1..10}; do command; done   

Beachten Sie den Unterstrich, anstatt eine Variable zu verwenden.

Ali Omary
quelle
9
Obwohl technisch, _ist ein Variablenname.
gniourf_gniourf
5

Wenn Sie dies regelmäßig tun, können Sie den folgenden Befehl ausführen, um es alle 1 Sekunde auf unbestimmte Zeit auszuführen. Sie können andere benutzerdefinierte Überprüfungen einrichten, um sie n-mal auszuführen.

watch -n 1 some_command

Wenn Sie eine visuelle Bestätigung der Änderungen wünschen, fügen Sie diese --differencesvor dem lsBefehl hinzu.

Laut der OSX-Manpage gibt es auch

Die Option --cumulative macht das Hervorheben "klebrig" und zeigt eine laufende Anzeige aller Positionen an, die sich jemals geändert haben. Mit der Option -t oder --no-title wird die Kopfzeile deaktiviert, in der das Intervall, der Befehl und die aktuelle Uhrzeit oben in der Anzeige sowie die folgende leere Zeile angezeigt werden.

Die Linux / Unix-Manpage finden Sie hier

Pankaj Singhal
quelle
5

Ich habe mit dieser Schleife gelöst, wobei Wiederholung eine Ganzzahl ist, die die Nummer der Schleifen darstellt

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

Noch eine Antwort: Verwenden Sie die Parametererweiterung für leere Parameter:

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

Getestet auf Centos 7 und MacOS.

jcollum
quelle
Das ist interessant. Können Sie uns näher erläutern, warum dies funktioniert?
Hassan Mahmud
1
Die Parametererweiterung wird n-mal ausgeführt, aber da der Parameter leer ist, ändert er nicht wirklich, was ausgeführt wird
jcollum
Die Suche nach Parametererweiterung und Curl hat zu keinen Ergebnissen geführt. Ich kenne Curl kaum. Es wäre großartig, wenn Sie mich auf einen Artikel oder einen anderen gebräuchlichen Namen für diese Funktion verweisen könnten. Vielen Dank.
Hassan Mahmud
1
Ich denke, dies könnte gnu.org/software/bash/manual/html_node/…
jcollum
3

Alle vorhandenen Antworten scheinen erforderlich zu sein bashund funktionieren nicht mit einem Standard-BSD-UNIX /bin/sh(z. B. kshunter OpenBSD ).

Der folgende Code sollte auf jedem BSD funktionieren:

$ 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
quelle
2

Ein bisschen naiv, aber das ist es, woran ich mich normalerweise auf Anhieb erinnere:

for i in 1 2 3; do
  some commands
done

Sehr ähnlich zu @ joe-kobergs Antwort. Seine ist besser, besonders wenn Sie viele Wiederholungen benötigen, nur schwieriger für mich, mich an andere Syntax zu erinnern, weil ich in den letzten Jahren nicht bashviel benutze . Ich meine zumindest nicht für Skripte.

akostadinov
quelle
1

Die Skriptdatei

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

echo "The argument is  arg: $1"

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

und die Ausgabe unten

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

Wie wäre es mit der alternativen Form der forin (bashref) Looping Constructs erwähnten ?

SamB
quelle
4
ihm einen Knochen werfen und ein Beispiel für die alternative Form geben?
Joe Koberg
0

For-Loops sind wahrscheinlich der richtige Weg, aber hier ist eine unterhaltsame Alternative:

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

Wenn Sie die Iterationsnummer als Parameter für Ihren Aufruf benötigen, verwenden Sie:

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

Bearbeiten: Es wurde zu Recht kommentiert, dass die oben angegebene Lösung nur mit einfachen Befehlsläufen (keine Pipes usw.) reibungslos funktionieren würde. Sie können immer eine verwendensh -c , um kompliziertere Dinge zu tun, aber es lohnt sich nicht.

Eine andere Methode, die ich normalerweise verwende, ist die folgende Funktion:

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

Jetzt können Sie es nennen als:

rep 3 10 echo iteration @

Die ersten beiden Zahlen geben den Bereich an. Das @wird in die Iterationsnummer übersetzt. Jetzt können Sie dies auch mit Rohren verwenden:

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

mit geben Sie die Anzahl der Dateien in den Verzeichnissen R1 .. R10.

Stacksia
quelle
1
Dies funktioniert nicht, wenn der Befehl komplexer ist als ein einfacher Befehl ohne Umleitungen.
Chepper
fair genug, aber siehe meine Bearbeitung oben. Die bearbeitete Methode verwendet ein sh-c und sollte daher auch mit der Umleitung funktionieren.
Stacksia
rep 1 2 touch "foo @"erstellt nicht zwei Dateien "foo 1" und "foo 2"; Vielmehr werden drei Dateien "foo", "1" und "2" erstellt. Es mag eine Möglichkeit geben, das Zitat mit printfund richtig zu zitieren %q, aber es wird schwierig sein, eine beliebige Folge von Wörtern korrekt in eine einzelne Zeichenfolge zu zitieren, an die übergeben werden soll sh -c.
Chepner
OP hat um mehr Prägnanz gebeten. Warum fügen Sie so etwas hinzu, wenn es bereits 5 präzisere Antworten gibt?
Zoska
Sobald Sie die Definition von repin Ihrem definiert haben .bashrc, werden weitere Aufrufe viel präziser.
Musiphil