Apakah ada cara yang lebih baik untuk menjalankan perintah N kali dalam bash?

304

Saya kadang-kadang menjalankan baris perintah bash seperti ini:

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

Untuk menjalankan some_commandbeberapa kali berturut-turut - 10 kali dalam hal ini.

Seringkali some_commandbenar-benar sebuah rantai perintah atau saluran pipa.

Apakah ada cara yang lebih ringkas untuk melakukan ini?

bstpierre
sumber
1
Selain jawaban bagus lainnya, Anda dapat menggunakan let ++nalih-alih n=$((n+1))(3 karakter kurang)
musiphil
1
Beberapa indikasi yang mana dari metode ini yang portabel akan lebih baik.
Faheem Mitha
serverfault.com/questions/273238/…
Ciro Santilli 郝海东 冠状 病 六四 事件 事件
3
Jika Anda bersedia mengganti kerang, zshmiliki repeat 10 do some_command; done.
chepner
1
@ JohnVandivier Saya baru saja mencobanya di bash dalam sebuah wadah docker 18,04 baru, sintaks asli seperti yang diposting dalam pertanyaan saya masih berfungsi. Mungkin Anda menjalankan shell selain bash, seperti / bin / sh, yang benar-benar putus-putus, dalam hal ini saya kembali sh: 1: [[: not found.
bstpierre

Jawaban:

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

Atau sebagai one-liner bagi mereka yang ingin menyalin dan menempel dengan mudah:

for run in {1..10}; do command; done
Joe Koberg
sumber
19
Jika Anda memiliki BANYAK pengulangan, formulir dengan for (n=0;n<k;n++))mungkin lebih baik; Saya menduga {1..k}akan memunculkan string dengan semua bilangan bulat yang dipisahkan oleh spasi.
Joe Koberg
@ Jo Koberg, terima kasih atas tipnya. Saya biasanya menggunakan N <100 jadi ini sepertinya bagus.
bstpierre
10
@ bstpierre: Formulir ekspansi brace tidak dapat menggunakan variabel (dengan mudah) untuk menentukan rentang di Bash.
Dijeda sampai pemberitahuan lebih lanjut.
5
Itu benar. Ekspansi Brace dilakukan sebelum ekspansi variabel menurut gnu.org/software/bash/manual/bashref.html#Brace-Expansion , sehingga tidak akan pernah melihat nilai variabel apa pun.
Joe Koberg
5
Sedih tentang ekspansi variabel. Saya menggunakan berikut ini untuk mengulang n-kali dan telah 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
membentuk
203

Menggunakan konstanta:

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

Menggunakan variabel (dapat menyertakan ekspresi matematika):

x=10; for ((n=0; n < (x / 2); n++)); do some_command; done
BatchyX
sumber
4
Ini adalah cara terdekat ke bahasa pemrograman ^^ - harus diterima!
Nam G VU
Yang ini lebih baik karena satu baris dan karenanya lebih mudah digunakan di dalam terminal
Kunok
Anda juga dapat menggunakan variabel, misalnyax=10 for ((n=0; n < $x; n++));
Jelphy
@Kunok Sebaiknya jalankan jawaban yang diterima dalam satu baris:for run in {1..10}; do echo "on run $run"; done
Douglas Adams
bagaimana jika nsudah diatur ke nilai tertentu? for (( ; n<10; n++ ))doesnt work / edit: terbaik mungkin menggunakan salah satu dari jawaban yang lain seperti while (( n++...satu
phil294
121

Cara sederhana lain untuk meretasnya:

seq 20 | xargs -Iz echo "Hi there"

jalankan gema 20 kali.


Perhatikan bahwa seq 20 | xargs -Iz echo "Hi there z"akan menampilkan:

Hai, 1
Hai, 2
...

mitnk
sumber
4
Anda bahkan tidak perlu 1 (bisa berlari seq 20)
Oleg Vaskevich
16
versi 2015:seq 20 | xargs -I{} echo "Hi there {}"
mitnk
4
Perhatikan bahwa zini bukan placeholder yang baik karena sangat umum sehingga bisa menjadi teks biasa dalam teks perintah. Gunakan {}akan lebih baik.
mitnk
4
Adakah cara untuk membuang nomor dari seq? jadi itu hanya akan mencetak Hi there 20 kali (tidak ada nomor)? EDIT: cukup gunakan -I{}dan kemudian tidak ada {}dalam perintah
Mike Graf
1
Cukup gunakan sesuatu, Anda yakin itu tidak akan di output, misalnya IIIIImaka itu terlihat bagus misalnya:seq 20 | xargs -IIIIII timeout 10 yourCommandThatHangs
rubo77
79

Jika Anda menggunakan shell zsh:

repeat 10 { echo 'Hello' }

Di mana 10 adalah berapa kali perintah akan diulang.

Wilson Silva
sumber
11
Meskipun ini bukan jawaban untuk pertanyaan (karena secara eksplisit menyebutkan bash), ini sangat berguna dan membantu saya untuk menyelesaikan masalah saya. +1
OutOfBound
2
Juga, jika Anda baru saja mengetiknya di terminal, Anda dapat menggunakan sesuatu sepertirepeat 10 { !! }
Renato Gomes
28

Menggunakan GNU Parallel yang bisa Anda lakukan:

parallel some_command ::: {1..1000}

Jika Anda tidak ingin nomor sebagai argumen dan hanya menjalankan satu pekerjaan sekaligus:

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

Tonton video intro untuk perkenalan cepat: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Ikuti tutorialnya ( http://www.gnu.org/software/parallel/parallel_tutorial.html ). Anda perintah baris dengan mencintaimu karenanya.

Ole Tange
sumber
Mengapa Anda menggunakan alat yang alasan keberadaannya, sebagaimana dibuktikan secara nyata oleh namanya, adalah untuk menjalankan berbagai hal secara paralel , dan kemudian memberikan argumen samar -j1 -N0yang mematikan paralelisme ????
Ross Presser
@RossPresser Itu pertanyaan yang sangat masuk akal. Alasannya adalah bahwa mungkin lebih mudah untuk mengekspresikan apa yang ingin Anda lakukan menggunakan sintaks Paralel GNU. Pikirkan: Bagaimana Anda menjalankan some_command1000 kali secara serial tanpa argumen? Dan: Bisakah Anda menyatakan lebih pendek dari itu parallel -j1 -N0 some_command ::: {1..1000}?
Ole Tange
Yah, saya kira Anda memiliki semacam titik, tetapi masih benar-benar terasa seperti menggunakan blok mesin yang dibuat halus sebagai palu. Jika saya cukup termotivasi, dan merasa mempertimbangkan calon penerus saya yang mencoba memahami apa yang saya lakukan, saya mungkin akan membungkus kebingungan dalam skrip shell dan hanya menyebutnya dengan repeat.sh repeatcount some_command. Untuk implementasi dalam skrip shell yang mungkin saya gunakan parallel, jika sudah diinstal pada mesin (mungkin tidak akan). Atau saya mungkin condong ke xargs karena itu tampaknya paling cepat ketika tidak ada pengalihan diperlukan oleh some_command.
Ross Presser
Saya minta maaf jika "pertanyaan yang sangat masuk akal" saya diungkapkan dengan cara yang tidak masuk akal.
Ross Presser
1
Dalam kasus khusus ini mungkin tidak begitu jelas. Ini menjadi lebih jelas jika Anda menggunakan lebih banyak opsi GNU Parallel, misalnya jika Anda ingin mencoba kembali perintah yang gagal 4 kali dan menyimpan output ke file yang berbeda:parallel -N0 -j1 --retries 4 --results outputdir/ some_command
Ole Tange
18

Fungsi sederhana dalam file konfigurasi bash ( ~/.bashrcsering) dapat bekerja dengan baik.

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

Sebut saja seperti ini.

$ runx 3 echo 'Hello world'
Hello world
Hello world
Hello world
baja
sumber
1
Suka itu. Sekarang jika itu dapat dibatalkan dengan ctrl + c, itu akan menjadi bagus
slashdottir
Mengapa menggunakan cara ini untuk menggemakan $ RANDOM n kali menampilkan angka yang sama?
BladeMight
3
Ini bekerja dengan sempurna. Satu-satunya hal yang perlu saya ubah adalah bukan hanya melakukan do ${*:2}saya menambahkan eval do eval ${*:2}untuk memastikan bahwa itu akan bekerja untuk menjalankan perintah yang tidak dimulai dengan executable. Misalnya, jika Anda ingin mengatur variabel lingkungan sebelum menjalankan perintah like SOME_ENV=test echo 'test'.
5_nd_5
12

xargsadalah cepat :

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

Pada Linux 64-bit modern, berikan:

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

Ini masuk akal, karena xargsperintah adalah proses asli tunggal yang memunculkan /usr/bin/trueperintah beberapa kali, bukan fordan whileloop yang semuanya ditafsirkan dalam Bash. Tentu saja ini hanya berfungsi untuk satu perintah; jika Anda perlu melakukan beberapa perintah dalam setiap iterasi, itu akan sama cepatnya, atau mungkin lebih cepat, daripada beralih sh -c 'command1; command2; ...'ke xargs

The -P1juga bisa berubah menjadi, katakanlah, -P8untuk menelurkan 8 proses secara paralel untuk mendapatkan dorongan besar lain dalam kecepatan.

Saya tidak tahu mengapa paralel GNU sangat lambat. Saya akan berpikir itu akan sebanding dengan xargs.

James Scriven
sumber
1
GNU Parallel melakukan cukup banyak pengaturan dan pembukuan: Mendukung string pengganti yang dapat diprogram, mendukung output sehingga output dari dua pekerjaan paralel tidak pernah tercampur, mendukung arah (Anda tidak dapat melakukan: xargs "echo> foo") dan karena tidak ditulis dalam bash itu harus menelurkan shell baru untuk melakukan pengalihan. Penyebab utama, bagaimanapun, adalah pada modul Perl IPC :: open3 - sehingga setiap pekerjaan untuk mendapatkan yang lebih cepat akan menghasilkan GNU Parallel yang lebih cepat.
Ole Tange
11

Bentuk lain dari contoh Anda:

n=0; while (( n++ < 10 )); do some_command; done
Dijeda sampai pemberitahuan lebih lanjut.
sumber
7

Untuk satu, Anda dapat membungkusnya dalam suatu fungsi:

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

Sebut saja seperti:

$ manytimes 3 echo "test" | tr 'e' 'E'
tEst
tEst
tEst
bta
sumber
2
Ini tidak akan berfungsi jika perintah untuk menjalankan lebih kompleks daripada perintah sederhana tanpa pengalihan.
chepner
Anda dapat membuatnya berfungsi untuk kasus yang lebih kompleks, tetapi Anda mungkin harus membungkus perintah dalam fungsi atau skrip.
bta
2
Di trsini menyesatkan dan bekerja pada output manytimes, bukan echo. Saya pikir Anda harus menghapusnya dari sampel.
Dmitry Ginzburg
7

xargs dan seq akan membantu

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

pandangan :

abon@abon:~$ __run_times 3  echo hello world
hello world
hello world
hello world
firejox
sumber
2
apa yang dilakukan oleh shift dan dash-ganda pada perintah? Apakah 'shift' benar-benar diperlukan?
sebs
2
Ini tidak akan berfungsi jika perintah lebih kompleks daripada perintah sederhana tanpa pengalihan.
chepner
Sangat lambat, echo $ ACAK 50 kali butuh 7 detik, dan meskipun begitu itu menampilkan nomor acak yang sama setiap kali dijalankan ...
BladeMight
6
for _ in {1..10}; do command; done   

Perhatikan garis bawah alih-alih menggunakan variabel.

Ali Omary
sumber
9
Padahal secara teknis, _adalah nama variabel.
gniourf_gniourf
5

Jika Anda OK melakukannya secara berkala, Anda dapat menjalankan perintah berikut untuk menjalankannya setiap 1 detik tanpa batas. Anda dapat menempatkan cek khusus lainnya untuk menjalankannya dan beberapa kali.

watch -n 1 some_command

Jika Anda ingin mendapatkan konfirmasi visual tentang perubahan, tambahkan --differencessebelum lsperintah.

Menurut halaman manual OSX, ada juga

Opsi --cumulative membuat penyorotan "lengket", menghadirkan tampilan semua posisi yang pernah berubah. Opsi -t atau --no-title mematikan header yang menunjukkan interval, perintah, dan waktu saat ini di bagian atas layar, serta baris kosong berikut.

Halaman manual Linux / Unix dapat ditemukan di sini

Pankaj Singhal
sumber
5

Saya diselesaikan dengan loop ini, di mana pengulangan adalah bilangan bulat yang mewakili nomor loop

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

Namun jawaban lain: Gunakan ekspansi parameter pada parameter kosong:

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

Diuji pada Centos 7 dan MacOS.

jcollum
sumber
Ini menarik, dapatkah Anda memberikan detail lebih lanjut mengapa ini berhasil?
Hassan Mahmud
1
Itu menjalankan ekspansi parameter n kali tetapi karena parameter kosong itu tidak benar-benar mengubah apa yang dijalankan
jcollum
Mencari ekspansi parameter dan ikal tidak membuahkan hasil. Saya hampir tidak tahu ikal. Akan luar biasa jika Anda bisa mengarahkan saya ke beberapa artikel atau mungkin nama umum lainnya untuk fitur ini. Terima kasih.
Hassan Mahmud
1
Saya pikir ini dapat membantu gnu.org/software/bash/manual/html_node/…
jcollum
3

Semua jawaban yang ada tampaknya membutuhkan bash, dan tidak berfungsi dengan BSD UNIX standar /bin/sh(misalnya, kshpada OpenBSD ).

Kode di bawah ini dapat digunakan pada 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
sumber
2

Agak naif tapi ini yang biasanya saya ingat di atas kepala saya:

for i in 1 2 3; do
  some commands
done

Sangat mirip dengan jawaban @ joe-koberg. Ini lebih baik terutama jika Anda membutuhkan banyak pengulangan, lebih sulit bagi saya untuk mengingat sintaks lain karena dalam beberapa tahun terakhir saya tidak menggunakan bashbanyak. Maksudku bukan untuk skrip setidaknya.

akostadinov
sumber
1

File skrip

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

echo "The argument is  arg: $1"

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

dan output di bawah ini

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

Bagaimana dengan bentuk alternatif yang fordisebutkan dalam (Bashref) Looping Constructs ?

SamB
sumber
4
lempar dia tulang dan berikan contoh bentuk pengganti?
Joe Koberg
0

Untuk loop mungkin cara yang tepat untuk melakukannya, tetapi di sini ada alternatif yang menyenangkan:

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

Jika Anda memerlukan nomor iterasi sebagai parameter untuk permohonan Anda, gunakan:

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

Sunting: Sudah benar dikomentari bahwa solusi yang diberikan di atas akan berfungsi dengan baik hanya dengan menjalankan perintah sederhana (tidak ada pipa, dll). Anda selalu dapat menggunakan sh -cuntuk melakukan hal-hal yang lebih rumit, tetapi tidak sepadan.

Metode lain yang saya gunakan biasanya adalah fungsi berikut:

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

sekarang Anda dapat menyebutnya sebagai:

rep 3 10 echo iteration @

Dua angka pertama memberikan kisaran. The @akan diterjemahkan ke nomor iterasi. Sekarang Anda dapat menggunakan ini dengan pipa juga:

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

dengan memberi Anda jumlah file dalam direktori R1 .. R10.

stacksia
sumber
1
Ini tidak akan berfungsi jika perintah lebih kompleks daripada perintah sederhana tanpa pengalihan.
chepner
cukup adil, tetapi lihat edit saya di atas. Metode yang diedit menggunakan sh -c, jadi harus bekerja dengan pengalihan juga.
stacksia
rep 1 2 touch "foo @"tidak akan membuat dua file "foo 1" dan "foo 2"; melainkan membuat tiga file "foo", "1", dan "2". Mungkin ada cara untuk mendapatkan kutipan yang benar menggunakan printfdan %q, tapi itu akan sulit untuk mendapatkan urutan kata-kata sewenang-wenang yang dikutip dengan benar ke dalam satu string untuk diteruskan sh -c.
chepner
OP meminta jawaban yang lebih ringkas , jadi mengapa Anda menambahkan sesuatu seperti ini, padahal sudah ada 5 jawaban yang lebih ringkas?
zoska
Setelah Anda mendefinisikan definisi repdalam Anda .bashrc, doa selanjutnya menjadi jauh lebih ringkas.
musiphil