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?


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


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

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

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
...


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.


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.


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

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.


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


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

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

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.


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


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

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.


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
$

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.


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$


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.


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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.