Bir komutu N kere bash'de çalıştırmanın daha iyi bir yolu var mı?


304

Bazen böyle bir bash komut satırı çalıştırın:

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

some_commandArka arkaya birkaç kez çalıştırmak için - bu durumda 10 kez.

Genellikle some_commandbir emir zinciri veya bir boru hattıdır.

Bunu yapmanın daha kısa bir yolu var mı?


1
Diğer güzel cevaplar yanı sıra, kullanabileceğiniz let ++nyerine n=$((n+1))(3 daha az karakterden).
musiphil

1
Bu yöntemlerden hangilerinin taşınabilir olduğuna dair bazı belirtiler iyi olurdu.
Faheem Mitha


3
Eğer kabukları değiştirmek isterseniz, zshvar repeat 10 do some_command; done.
Chepner

1
@JohnVandivier Yeni bir 18.04 liman işçisi konteynırında bash denedim, soruma gönderilen orijinal sözdizimi hala çalışıyor. Belki bash dışında bir kabuk çalıştırıyorsunuz, / bin / sh gibi, bu gerçekten çizgi, bu durumda geri dönüyorum sh: 1: [[: not found.
bstpierre

Yanıtlar:


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

Veya kolayca kopyalayıp yapıştırmak isteyenler için tek astar olarak:

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

19
Çok fazla yineleme varsa, form for (n=0;n<k;n++))daha iyi olabilir; Ben {1..k}boşluk ile ayrılmış tüm bu tamsayılar ile bir dize gerçekleştirecektir şüpheli .
Joe Koberg

@Joe Koberg, bahşiş için teşekkürler. Ben genellikle N <100 kullanıyorum, bu yüzden bu iyi görünüyor.
bstpierre

10
@bstpierre: Brace genişletme formu, Bash'deki aralığı belirtmek için değişkenleri (kolayca) kullanamaz.
sonraki duyuruya kadar duraklatıldı.

5
Bu doğru. Brace genişletmesi, gnu.org/software/bash/manual/bashref.html#Brace-Expansion'a göre değişken genişletmeden önce gerçekleştirilir , böylece hiçbir değişkenin değerini asla görmez.
Joe Koberg

5
Değişken genişleme hakkında üzücü bir şey. Ben n-döngü için aşağıdaki kullanıyorum ve sayıları oluşturdu: 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 15:14

203

Bir sabit kullanma:

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

Bir değişken kullanma (matematik ifadeleri içerebilir):

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

4
Bu programlama diline en yakın yoldur ^ ^ - kabul edilen dil olmalı!
Nam G VU

Bu daha iyidir çünkü tek bir hattır ve bu nedenle terminal içinde kullanımı daha kolaydır
Kunok

Ayrıca bir değişken de kullanabilirsiniz, örneğinx=10 for ((n=0; n < $x; n++));
Jelphy

@Kunok Kabul edilen yanıtı bir satırda çalıştırmak da kabul edilebilir:for run in {1..10}; do echo "on run $run"; done
Douglas Adams

nönceden belirli bir değere ayarlanmışsa ne olur ? for (( ; n<10; n++ ))çalışmıyor / düzenle: en iyi biri gibi diğer cevaplardan while (( n++...birini kullanın
phil294

121

Bunu kesmek için başka bir basit yol:

seq 20 | xargs -Iz echo "Hi there"

yankı 20 kez çalıştırın.


seq 20 | xargs -Iz echo "Hi there z"Çıktı alacağına dikkat edin :

Merhaba orada 1
Merhaba orada 2
...


4
1'e bile ihtiyacın yok (sadece koşabilir seq 20)
Oleg Vaskevich

16
sürüm 2015:seq 20 | xargs -I{} echo "Hi there {}"
mitnk

4
Bunun ziyi bir yer tutucu olmadığına dikkat edin , çünkü komut metninde normal bir metin olabilir. Kullanımı {}daha iyi olacaktır.
mitnk

4
Numarayı atmanın herhangi bir yolu var seqmı? yani sadece Hi there 20 kez basar (numara yok)? EDIT: sadece kullanın -I{}ve sonra {}komut herhangi bir şey yok
Mike Graf

1
Sadece bir şey kullanın, çıktıda olmayacağından IIIIIseq 20 | xargs -IIIIII timeout 10 yourCommandThatHangs
eminsiniz

79

Zsh kabuğunu kullanıyorsanız:

repeat 10 { echo 'Hello' }

10, komutun kaç kez tekrarlanacağıdır.


11
Bu sorunun cevabı olmasa da (açıkça bash'dan bahsettiği için), çok yararlı ve sorunumu çözmeme yardımcı oldu. +1
OutOfBound

2
Ayrıca, sadece terminalde yazdıysanız, gibi bir şey kullanabilirsinizrepeat 10 { !! }
Renato Gomes

28

GNU Paralel kullanarak şunları yapabilirsiniz:

parallel some_command ::: {1..1000}

Sayıyı bağımsız değişken olarak istemiyorsanız ve aynı anda yalnızca tek bir iş çalıştırıyorsanız:

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

Hızlı bir giriş için giriş videosunu izleyin: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Eğiticiyi gözden geçirin ( http://www.gnu.org/software/parallel/parallel_tutorial.html ). Bunun için seni sevgiyle komut satırı.


Eğer olarak açıkça adıyla kanıtlanmış olan çok nedeni varlığı için bir araç, kullanmak Neden şeyler çalıştırmaktır paralel olarak ve daha sonra bunu şifreli argments vermek -j1 -N0olduğunu paralellik kapatın ????
Ross Presser

@RossPresser Bu çok makul bir soru. Bunun nedeni, GNU Paralel sözdizimini kullanarak ne yapmak istediğinizi ifade etmenin daha kolay olabilmesidir. Düşünüyorum: some_command1000 kez seri olarak argüman olmadan nasıl çalışırsınız ? Ve: Bunu daha kısa ifade edebilir misiniz parallel -j1 -N0 some_command ::: {1..1000}?
Ole Tange

Sanırım bir noktanız var, ama yine de bir çekiç olarak ince hazırlanmış bir motor bloğu kullanmak gibi geliyor. Yeterince motive olsaydım ve gelecekteki haleflerimin ne yaptığımı anlamaya çalıştığını düşünürsem, muhtemelen karışıklığı bir kabuk betiğine sarar ve sadece onunla çağırırdım repeat.sh repeatcount some_command. Kabuk betiğinde uygulama paralleliçin, zaten makineye yüklenmişse kullanabilirim (muhtemelen olmaz). Veya xargs'a yönelebilirim, çünkü yeniden yönlendirmeye ihtiyaç duyulmadığında en hızlı gibi görünüyor some_command.
Ross Presser

"Çok mantıklı sorum" makul olmayan bir şekilde ifade edildiğinde özür dilerim.
Ross Presser

1
Bu özel durumda çok açık olmayabilir. Daha fazla GNU Paralel seçeneği kullanırsanız daha belirgin hale gelir, örneğin başarısız komutları 4 kez tekrar denemek ve çıktıyı farklı dosyalara kaydetmek istiyorsanız:parallel -N0 -j1 --retries 4 --results outputdir/ some_command
Ole Tange

18

Bash yapılandırma dosyasındaki basit bir işlev ( ~/.bashrcgenellikle) iyi çalışabilir.

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

Böyle söyle.

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

1
Beğendim. Şimdi ctrl + c ile iptal edilebilirse, bu harika olurdu
slashdottir

N $ $ RANDOM n kere yankılamak için bu yolu kullanmanız aynı sayıyı gösterir
BladeMight

3
Bu mükemmel çalışıyor. Değiştirmem gereken tek şey, sadece executables ile başlamayan komutları çalıştırmak için çalışacağından emin olmak için do ${*:2}eval ekledi do eval ${*:2}. Örneğin, gibi bir komut çalıştırmadan önce bir ortam değişkeni ayarlamak istiyorsanız SOME_ENV=test echo 'test'.
5_nd_5

12

xargsolduğu hızlı :

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

Modern bir 64-bit Linux'ta:

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

xargsKomut, Bash'ta yorumlanan ve döngüleri /usr/bin/trueyerine komutu birden çok kez üreten tek bir yerel işlem olduğu için bu mantıklıdır . Tabii ki bu sadece tek bir komut için işe yarıyor; her bir yinelemede döngü için birden fazla komut yapmanız gerekiyorsa, xargs'e geçmek kadar hızlı veya belki daha hızlı olacaktır.forwhilesh -c 'command1; command2; ...'

-P1Ayrıca, diyelim ki, şeklinde değiştirilebilir -P8hız başka büyük bir destek almak için paralel 8 süreçlerini yumurtlamaya.

GNU paralelinin neden bu kadar yavaş olduğunu bilmiyorum. Bunun xargs ile karşılaştırılabilir olacağını düşünürdüm.


1
GNU Parallel çok fazla kurulum ve defter tutma yapar: Programlanabilir değiştirme dizelerini destekler, çıktıyı tamponlar, böylece iki paralel işin çıktısı asla karıştırılmaz, yönü destekler (Yapamazsınız: xargs "echo> foo") ve değil bash ile yazılmış yeniden yönlendirme yapmak için yeni bir kabuk ortaya çıkarmak zorundadır. Ancak, birincil neden Perl modülü IPC :: open3'tür.
Ole Tange


7

Birincisi, bir işlevde tamamlayabilirsiniz:

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

Şöyle çağırın:

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

2
Çalıştırılacak komut, yeniden yönlendirme içermeyen basit bir komuttan daha karmaşıksa bu çalışmaz.
chepner

Daha karmaşık durumlarda çalışmasını sağlayabilirsiniz, ancak komutu bir işlevde veya komut dosyasında sarmanız gerekebilir.
bta

2
trBurada yanıltıcıdır ve çıkışında üzerinde çalışıyor manytimes, değil echo. Bence numuneden çıkarmalısın.
Dmitry Ginzburg

7

xargs ve seq yardımcı olacaktır

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

görünüm :

abon@abon:~$ __run_times 3  echo hello world
hello world
hello world
hello world

2
komuttaki kaydırma ve çift çizgi ne yapar? 'Vardiya' gerçekten gerekli mi?
sebs

2
Komut, yönlendirmeleri olmayan basit bir komuttan daha karmaşıksa bu çalışmaz.
chepner

Çok yavaş, yankı $ RANDOM 50 kez 7 saniye sürdü ve buna rağmen her çalışmada aynı rasgele sayıyı gösterir ...
BladeMight

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

Değişken kullanmak yerine alt çizgiyi not edin.


9
Teknik olarak _değişken bir isim olsa da.
gniourf_gniourf

5

Düzenli olarak bunu yapmakta sorun yaşıyorsanız, süresiz olarak her 1 saniyede bir çalıştırmak için aşağıdaki komutu çalıştırabilirsiniz. Başka özel denetimleri, n kez çalıştırmak için koyabilirsiniz.

watch -n 1 some_command

Değişikliklerin görsel olarak onaylanmasını istiyorsanız --differences, lskomutun önüne ekleyin .

OSX kılavuz sayfasına göre,

--Cumulative seçeneği "yapışkan" vurguyu yaparak, değişen tüm konumların çalışan bir görüntüsünü sunar. -T veya --no-title seçeneği, ekranın üstündeki aralık, komut ve geçerli saati ve aşağıdaki boş satırı gösteren üstbilgiyi kapatır.

Linux / Unix kılavuz sayfası burada bulunabilir


5

Bu döngü ile çözdüm, burada tekrar döngü sayısını temsil eden bir tam sayıdır

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

4

Başka bir cevap: Boş parametrelerde parametre genişletmeyi kullanın:

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

Centos 7 ve MacOS üzerinde test edildi.


Bu ilginç, neden işe yaradığına dair biraz daha ayrıntı verebilir misiniz?
Hassan Mahmud

1
Parametre genişletmeyi n kez çalıştırıyor, ancak parametre boş olduğundan çalıştırılanı gerçekten değiştirmiyor
jcollum

Parametre genişletme ve kıvrılma araması sonuç vermedi. Kıvrımı zar zor biliyorum. Beni bazı makalelere veya belki de bu özellik için başka bir ortak isme yönlendirebilirseniz harika olurdu. Teşekkürler.
Hassan Mahmud


3

Varolan yanıtların tümü bashstandart BSD UNIX /bin/sh(ör ksh. OpenBSD'de ) gerektiriyor gibi görünmüyor .

Aşağıdaki kod herhangi bir BSD üzerinde çalışmalıdır:

$ 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

Biraz saf ama bu genellikle başımın üstünden hatırlıyorum:

for i in 1 2 3; do
  some commands
done

@ Joe-koberg'in cevabına çok benzer. Özellikle daha fazla tekrara ihtiyacınız varsa daha iyidir, diğer sözdizimini hatırlamam daha zor çünkü son yıllarda bashçok fazla kullanmıyorum . En azından senaryo yazmak için değil.


1

Komut dosyası

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

echo "The argument is  arg: $1"

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

ve aşağıdaki çıktı

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


0

Döngüler muhtemelen bunu yapmanın doğru yoludur, ancak işte eğlenceli bir alternatif:

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

Çağrınız için parametre olarak yineleme numarasına ihtiyacınız varsa, şunu kullanın:

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

Düzenleme: Haklı olarak yukarıda verilen çözüm sadece basit komut çalıştırma (sorunsuz, vb) ile sorunsuz çalışması yorumlandı. her zaman birsh -cdaha karmaşık şeyler yapmak için , ancak buna değmez.

Normalde kullandığım başka bir yöntem aşağıdaki işlevdir:

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

şimdi şöyle diyebilirsiniz:

rep 3 10 echo iteration @

İlk iki sayı aralığı verir. @İterasyon sayısına çevrilir alacak. Şimdi bunu borularla da kullanabilirsiniz:

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

size R1 .. R10 dizinlerindeki dosya sayısını verir.


1
Komut, yönlendirmeleri olmayan basit bir komuttan daha karmaşıksa bu çalışmaz.
chepner

yeterli, ancak yukarıdaki düzenlememe bakın. Düzenlenen yöntem bir sh -c kullanır, bu nedenle yeniden yönlendirme ile de çalışmalıdır.
stacksia

rep 1 2 touch "foo @""foo 1" ve "foo 2" adlı iki dosya oluşturmaz; bunun yerine üç dosya "foo", "1" ve "2" oluşturur. Alıntıyı doğru şekilde kullanmanın bir yolu olabilir printfve %qancak geçmesi gereken tek bir dizeye doğru bir şekilde alıntılanan kelimelerin rastgele bir dizisini elde etmek zor olacaktır sh -c.
chepner

OP daha kısa ve öz istedi , peki 5 özlü cevap daha varken neden böyle bir şey ekliyorsunuz?
zoska

Eğer tanımını tanımladıktan sonra repsenin de .bashrcdaha çağırmaları çok daha fazla özlü hale gelir.
musiphil
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.