Existe-t-il une meilleure façon d'exécuter une commande N fois en bash?

304

J'exécute parfois une ligne de commande bash comme celle-ci:

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

Pour exécuter some_commandplusieurs fois de suite - 10 fois dans ce cas.

Il some_commands'agit souvent d' une chaîne de commandes ou d'un pipeline.

Existe-t-il une manière plus concise de procéder?

bstpierre
la source
1
Outre les autres belles réponses, vous pouvez utiliser à la let ++nplace de n=$((n+1))(3 caractères de moins).
musiphil
1
Certaines indications sur lesquelles de ces méthodes sont portables seraient intéressantes.
Faheem Mitha
serverfault.com/questions/273238/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
3
Si vous êtes prêt à changer de coquille, zshoui repeat 10 do some_command; done.
chepner
1
@JohnVandivier Je viens de l'essayer en bash dans un nouveau docker 18.04, la syntaxe originale telle que publiée dans ma question fonctionne toujours. Peut-être que vous utilisez un shell autre que bash, comme / bin / sh, qui est vraiment un tiret, auquel cas je reviens sh: 1: [[: not found.
bstpierre

Réponses:

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

Ou comme une ligne pour ceux qui veulent copier et coller facilement:

for run in {1..10}; do command; done
Joe Koberg
la source
19
Si vous avez BEAUCOUP d'itérations, le formulaire avec for (n=0;n<k;n++))peut être meilleur; Je suspecte de {1..k}matérialiser une chaîne avec tous ces entiers séparés par des espaces.
Joe Koberg
@Joe Koberg, merci pour l'astuce. J'utilise généralement N <100, donc cela semble bien.
bstpierre
10
@bstpierre: Le formulaire d'expansion d'accolade ne peut pas utiliser (facilement) des variables pour spécifier la plage dans Bash.
pause jusqu'à nouvel ordre.
5
C'est vrai. L'expansion des accolades est effectuée avant l'expansion des variables selon gnu.org/software/bash/manual/bashref.html#Brace-Expansion , ainsi il ne verra jamais les valeurs des variables.
Joe Koberg
5
Triste chose à propos de l'expansion variable. J'utilise ce qui suit pour boucler n fois et j'ai formaté des nombres: 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

En utilisant une constante:

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

Utilisation d'une variable (peut inclure des expressions mathématiques):

x=10; for ((n=0; n < (x / 2); n++)); do some_command; done
BatchyX
la source
4
C'est le moyen le plus proche du langage de programmation ^^ - devrait être celui accepté!
Nam G VU
Celui-ci est meilleur car il s'agit d'une seule ligne et donc plus facile à utiliser dans le terminal
Kunok
Vous pouvez également utiliser une variable, par exemplex=10 for ((n=0; n < $x; n++));
Jelphy
@Kunok Il est tout aussi acceptable d'exécuter la réponse acceptée en une seule ligne:for run in {1..10}; do echo "on run $run"; done
Douglas Adams
que faire si nest déjà défini sur une valeur spécifique? for (( ; n<10; n++ ))ne fonctionne pas / ne modifie pas: il est préférable d'utiliser l'une des autres réponses comme celle- while (( n++...
phil294
121

Un autre moyen simple de le pirater:

seq 20 | xargs -Iz echo "Hi there"

exécuter l'écho 20 fois.


Notez que cela seq 20 | xargs -Iz echo "Hi there z"produirait:

Salut à 1
Salut à 2
...

mitnk
la source
4
vous n'avez même pas besoin du 1 (peut simplement courir seq 20)
Oleg Vaskevich
16
version 2015:seq 20 | xargs -I{} echo "Hi there {}"
mitnk
4
Notez que ce zn'est pas un bon espace réservé car il est si courant qu'il peut s'agir d'un texte normal dans le texte de la commande. L'utilisation {}sera meilleure.
mitnk
4
Est-il possible de supprimer le numéro seq? il imprimerait donc Hi there 20 fois (pas de numéro)? EDIT: utilisez simplement -I{}et n'en avez pas {}dans la commande
Mike Graf
1
Utilisez simplement quelque chose, vous êtes sûr qu'il ne sera pas dans la sortie, par exemple IIIIIalors il a l'air bien par exemple:seq 20 | xargs -IIIIII timeout 10 yourCommandThatHangs
rubo77
79

Si vous utilisez le shell zsh:

repeat 10 { echo 'Hello' }

Où 10 est le nombre de fois que la commande sera répétée.

Wilson Silva
la source
11
Bien que ce ne soit pas une réponse à la question (car il mentionne explicitement bash), il est très utile et m'a aidé à résoudre mon problème. +1
OutOfBound
2
De plus, si vous venez de le taper dans le terminal, vous pouvez utiliser quelque chose commerepeat 10 { !! }
Renato Gomes
28

En utilisant GNU Parallel, vous pouvez faire:

parallel some_command ::: {1..1000}

Si vous ne voulez pas le nombre comme argument et que vous n'exécutez qu'un seul travail à la fois:

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

Regardez la vidéo d'introduction pour une introduction rapide: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Parcourez le didacticiel ( http://www.gnu.org/software/parallel/parallel_tutorial.html ). Vous commandez en ligne avec amour pour vous.

Ole Tange
la source
Pourquoi voudriez-vous utiliser un outil dont la raison même de l'existence, comme le prouve manifestement son nom, est d'exécuter les choses en parallèle , puis de lui donner des arguments cryptiques -j1 -N0qui désactivent le parallélisme ????
Ross Presser
@RossPresser C'est une question très raisonnable. La raison en est qu'il peut être plus facile d'exprimer ce que vous voulez faire en utilisant la syntaxe GNU Parallel. Pensez: Comment feriez-vous some_command1000 fois en série sans argument? Et: Pouvez-vous exprimer cela plus court que parallel -j1 -N0 some_command ::: {1..1000}?
Ole Tange
Eh bien, je suppose que vous avez une sorte de point, mais cela ressemble toujours vraiment à l'utilisation d'un bloc moteur finement conçu comme un marteau. Si j'étais suffisamment motivé et que je me sentais prévenant de mes futurs successeurs essayant de comprendre ce que je faisais, j'envelopperais probablement la confusion dans un script shell et je l'appellerais simplement avec repeat.sh repeatcount some_command. Pour la mise en œuvre dans le script shell, je pourrais utiliser parallel, s'il était déjà installé sur la machine (ce ne serait probablement pas le cas). Ou je pourrais graviter vers xargs car cela semble être le plus rapide lorsqu'aucune redirection n'est nécessaire par some_command.
Ross Presser
Je m'excuse si ma «question très raisonnable» a été exprimée de manière déraisonnable.
Ross Presser
1
Dans ce cas précis, cela peut ne pas être aussi évident. Cela devient plus évident si vous utilisez plus d'options de GNU Parallel, par exemple si vous voulez réessayer 4 fois les commandes ayant échoué et enregistrer la sortie dans différents fichiers:parallel -N0 -j1 --retries 4 --results outputdir/ some_command
Ole Tange
18

Une fonction simple dans le fichier de configuration bash ( ~/.bashrcsouvent) pourrait bien fonctionner.

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

Appelez ça comme ça.

$ runx 3 echo 'Hello world'
Hello world
Hello world
Hello world
acier
la source
1
J'aime ça. Maintenant, si cela pouvait être annulé avec ctrl + c, ce serait génial
slashdottir
Pourquoi utiliser cette méthode pour faire écho à $ RANDOM n fois affiche le même nombre?
BladeMight
3
Cela fonctionne parfaitement. La seule chose que je devais changer était au lieu de simplement le faire, do ${*:2}j'ai ajouté eval do eval ${*:2}pour m'assurer qu'il fonctionnerait pour l'exécution de commandes qui ne commencent pas par des exécutables. Par exemple, si vous souhaitez définir une variable d'environnement avant d'exécuter une commande comme SOME_ENV=test echo 'test'.
5_nd_5
12

xargsest rapide :

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

Sur un Linux 64 bits moderne, donne:

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

Cela a du sens, car la xargscommande est un processus natif unique qui génère la /usr/bin/truecommande plusieurs fois, au lieu des boucles foret whilequi sont toutes interprétées dans Bash. Bien sûr, cela ne fonctionne que pour une seule commande; si vous avez besoin de faire plusieurs commandes à chaque itération de la boucle, ce sera tout aussi rapide, ou peut-être plus rapide, que de passer sh -c 'command1; command2; ...'à xargs

Le -P1pourrait également être modifié pour, par exemple, -P8engendrer 8 processus en parallèle pour obtenir un autre coup de pouce de vitesse.

Je ne sais pas pourquoi le parallèle GNU est si lent. J'aurais pensé que ce serait comparable aux xargs.

James Scriven
la source
1
GNU Parallel fait beaucoup de configuration et de tenue de livres: il prend en charge les chaînes de remplacement programmables, il met en mémoire tampon la sortie de sorte que la sortie de deux travaux parallèles n'est jamais mélangée, il prend en charge la direction (vous ne pouvez pas faire: xargs "echo> foo") et puisqu'il ne l'est pas écrit en bash, il doit générer un nouveau shell pour faire la redirection. Cependant, la cause principale est dans le module Perl IPC :: open3 - donc tout travail pour obtenir cela plus rapidement se traduira par un GNU Parallel plus rapide.
Ole Tange
11

Une autre forme de votre exemple:

n=0; while (( n++ < 10 )); do some_command; done
En pause jusqu'à nouvel ordre.
la source
7

D'une part, vous pouvez le résumer dans une fonction:

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

Appelez ça comme:

$ manytimes 3 echo "test" | tr 'e' 'E'
tEst
tEst
tEst
bta
la source
2
Cela ne fonctionnera pas si la commande à exécuter est plus complexe qu'une simple commande sans redirection.
chepner
Vous pouvez le faire fonctionner pour des cas plus complexes, mais vous devrez peut-être encapsuler la commande dans une fonction ou un script.
bta
2
L' trici est trompeur et il fonctionne sur la sortie de manytimes, non echo. Je pense que vous devriez le retirer de l'échantillon.
Dmitry Ginzburg,
7

xargs et seq aideront

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

la vue :

abon@abon:~$ __run_times 3  echo hello world
hello world
hello world
hello world
firejox
la source
2
que font le décalage et le double tiret dans la commande? Le «changement» est-il vraiment nécessaire?
sebs
2
Cela ne fonctionnera pas si la commande est plus complexe qu'une simple commande sans redirection.
chepner
Très lent, l'écho $ RANDOM 50 fois a pris 7 secondes, et même ainsi, il affiche le même nombre aléatoire à chaque exécution ...
BladeMight
6
for _ in {1..10}; do command; done   

Notez le trait de soulignement au lieu d'utiliser une variable.

Ali Omary
la source
9
Bien que techniquement, _c'est un nom de variable.
gniourf_gniourf
5

Si vous êtes d'accord pour le faire périodiquement, vous pouvez exécuter la commande suivante pour l'exécuter toutes les 1 s indéfiniment. Vous pouvez mettre en place d'autres contrôles personnalisés pour l'exécuter n nombre de fois.

watch -n 1 some_command

Si vous souhaitez avoir une confirmation visuelle des modifications, ajoutez-les --differencesavant la lscommande.

Selon la page de manuel OSX, il y a aussi

L'option --cumulative rend la surbrillance "collante", présentant un affichage en cours de toutes les positions qui ont changé. L'option -t ou --no-title désactive l'en-tête affichant l'intervalle, la commande et l'heure actuelle en haut de l'écran, ainsi que la ligne vierge suivante.

La page de manuel Linux / Unix peut être trouvée ici

Pankaj Singhal
la source
5

J'ai résolu avec cette boucle, où la répétition est un entier qui représente le nombre de boucles

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

Encore une autre réponse: utilisez l'expansion des paramètres sur les paramètres vides:

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

Testé sur Centos 7 et MacOS.

jcollum
la source
Ceci est intéressant, pouvez-vous fournir plus de détails sur la raison pour laquelle cela fonctionne?
Hassan Mahmud
1
Il exécute l'extension de paramètre n fois, mais comme le paramètre est vide, il ne change pas réellement ce qui est exécuté
jcollum
La recherche de l'extension des paramètres et de la boucle n'a donné aucun résultat. Je connais à peine le curl. Ce serait génial si vous pouviez me pointer vers un article ou peut-être un autre nom commun pour cette fonctionnalité. Merci.
Hassan Mahmud
1
Je pense que cela peut aider gnu.org/software/bash/manual/html_node/…
jcollum
3

Toutes les réponses existantes semblent nécessiter bashet ne fonctionnent pas avec un BSD UNIX standard /bin/sh(par exemple, kshsur OpenBSD ).

Le code ci-dessous devrait fonctionner sur n'importe quel 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
la source
2

Un peu naïf mais c'est ce dont je me souviens habituellement du haut de ma tête:

for i in 1 2 3; do
  some commands
done

Très similaire à la réponse de @ joe-koberg. La sienne est meilleure, surtout si vous avez besoin de nombreuses répétitions, juste plus difficile pour moi de me souvenir d'une autre syntaxe car ces dernières années, je n'utilise pas bashbeaucoup. Je veux dire pas pour les scripts au moins.

akostadinov
la source
1

Le fichier 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

et la sortie ci-dessous

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

Que diriez - vous de la forme alternative de formentionné dans (bashref) Looping Constructs ?

SamB
la source
4
lui jeter un os et donner un exemple de la forme alternative?
Joe Koberg
0

Les boucles sont probablement la bonne façon de le faire, mais voici une alternative amusante:

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

Si vous avez besoin du numéro d'itération comme paramètre pour votre appel, utilisez:

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

Edit: Il a été à juste titre commenté que la solution donnée ci-dessus ne fonctionnerait correctement qu'avec des exécutions de commandes simples (pas de tuyaux, etc.). vous pouvez toujours utiliser unsh -c pour faire des choses plus compliquées, mais cela n'en vaut pas la peine.

Une autre méthode que j'utilise généralement est la fonction suivante:

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

vous pouvez maintenant l'appeler comme:

rep 3 10 echo iteration @

Les deux premiers chiffres donnent la plage. Le @sera traduit au numéro d'itération. Maintenant, vous pouvez également l'utiliser avec des tuyaux:

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

vous donne le nombre de fichiers dans les répertoires R1 .. R10.

stacksia
la source
1
Cela ne fonctionnera pas si la commande est plus complexe qu'une simple commande sans redirection.
chepner
assez juste, mais voir ma modification ci-dessus. La méthode éditée utilise un sh -c, donc devrait également fonctionner avec la redirection.
stacksia
rep 1 2 touch "foo @"ne crée pas deux fichiers "foo 1" et "foo 2"; il crée plutôt trois fichiers "foo", "1" et "2". Il peut y avoir un moyen d'obtenir la bonne citation en utilisant printfet %q, mais il va être difficile d'obtenir une séquence arbitraire de mots correctement cités dans une seule chaîne pour passer sh -c.
chepner
OP a demandé plus de concision , alors pourquoi ajoutez-vous quelque chose comme ça, alors qu'il y a déjà 5 réponses plus concises?
zoska
Une fois que vous avez défini la définition de repdans votre .bashrc, les appels ultérieurs deviennent beaucoup plus concis.
musiphil