Có cách nào tốt hơn để chạy lệnh N lần trong bash không?


304

Thỉnh thoảng tôi chạy một dòng lệnh bash như thế này:

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

Để chạy some_commandmột số lần liên tiếp - 10 lần trong trường hợp này.

Thường some_commandthực sự là một chuỗi các lệnh hoặc một đường ống dẫn.

Có cách nào ngắn gọn hơn để làm điều này?


1
Bên cạnh những câu trả lời hay khác, bạn có thể sử dụng let ++nthay vì n=$((n+1))(3 ký tự ít hơn).
musiphil

1
Một số chỉ dẫn trong số các phương pháp này là di động sẽ tốt đẹp.
Faheem Mitha


3
Nếu bạn sẵn sàng thay đổi vỏ, zshrepeat 10 do some_command; done.
chepner 3/03/2016

1
@JohnVandivier Tôi vừa thử nó trong bash trong một container 18.04 mới, cú pháp ban đầu như được đăng trong câu hỏi của tôi vẫn hoạt động. Có thể bạn đang chạy một trình bao khác ngoài bash, như / bin / sh, thực sự rất tuyệt, trong trường hợp đó tôi sẽ quay lại sh: 1: [[: not found.
bstpierre

Câu trả lời:


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

Hoặc dưới dạng một lớp lót cho những người muốn sao chép và dán dễ dàng:

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

19
Nếu bạn có RẤT NHIỀU lần lặp, biểu mẫu với for (n=0;n<k;n++))có thể tốt hơn; Tôi nghi ngờ {1..k}sẽ cụ thể hóa một chuỗi với tất cả các số nguyên được phân tách bằng dấu cách.
Joe Koberg

@Joe Koberg, cảm ơn vì tiền boa. Tôi thường sử dụng N <100 nên điều này có vẻ tốt.
bstpierre

10
@bstpierre: Biểu mẫu mở rộng dấu ngoặc không thể sử dụng các biến (dễ dàng) để chỉ định phạm vi trong Bash.
Tạm dừng cho đến khi có thông báo mới.

5
Điều đó đúng. Mở rộng cú đúp được thực hiện trước khi mở rộng biến theo gnu.org/software/bash/manual/bashref.html#Brace-Expansion , do đó nó sẽ không bao giờ nhìn thấy các giá trị của bất kỳ biến nào.
Joe Koberg

5
Điều đáng buồn về sự mở rộng biến. Tôi đang sử dụng theo dõi để lặp lại n lần và có các số được tạo thành: 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

Sử dụng hằng số:

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

Sử dụng một biến (có thể bao gồm các biểu thức toán học):

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

4
Đây là cách gần nhất với ngôn ngữ lập trình ^^ - nên là ngôn ngữ được chấp nhận!
Nam G VU

Cái này tốt hơn bởi vì nó là một dòng duy nhất và do đó dễ sử dụng hơn trong thiết bị đầu cuối
Kunok

Bạn cũng có thể sử dụng một biến, ví dụx=10 for ((n=0; n < $x; n++));
Jelphy

@Kunok Việc chạy câu trả lời được chấp nhận trong một dòng là chấp nhận được:for run in {1..10}; do echo "on run $run"; done
Douglas Adams

Điều gì nếu nđã được đặt thành một giá trị cụ thể? for (( ; n<10; n++ ))doesnt work / chỉnh sửa: tốt nhất có thể sử dụng một trong những câu trả lời khác như while (( n++...một
phil294

121

Một cách đơn giản khác để hack nó:

seq 20 | xargs -Iz echo "Hi there"

chạy vang 20 lần.


Thông báo rằng seq 20 | xargs -Iz echo "Hi there z" sẽ xuất ra:

Xin chào 1
Xin chào 2
...


4
bạn thậm chí không cần 1 (chỉ có thể chạy seq 20)
Oleg Vaskevich

16
phiên bản 2015:seq 20 | xargs -I{} echo "Hi there {}"
mitnk

4
Lưu ý rằng đó zkhông phải là một trình giữ chỗ tốt bởi vì nó rất phổ biến có thể là một văn bản thông thường trong văn bản lệnh. Sử dụng {}sẽ tốt hơn.
mitnk

4
Bất kỳ cách nào để loại bỏ số từ seq? Vì vậy, nó sẽ chỉ in Hi there 20 lần (không có số)? EDIT: chỉ sử dụng -I{}và sau đó không có bất kỳ {}lệnh nào
Mike Graf

1
Chỉ cần sử dụng một cái gì đó, bạn chắc chắn rằng nó sẽ không ở đầu ra, ví dụ IIIIIsau đó nó trông rất đẹp chẳng hạn:seq 20 | xargs -IIIIII timeout 10 yourCommandThatHangs
rubo77

79

Nếu bạn đang sử dụng shell zsh:

repeat 10 { echo 'Hello' }

Trong đó 10 là số lần lệnh sẽ được lặp lại.


11
Mặc dù nó không phải là một câu trả lời cho câu hỏi (vì nó rõ ràng đề cập đến bash), nhưng nó rất hữu ích và giúp tôi giải quyết vấn đề của mình. +1
OutOfBound

2
Ngoài ra, nếu bạn vừa gõ nó vào thiết bị đầu cuối, bạn có thể sử dụng một cái gì đó nhưrepeat 10 { !! }
Renato Gomes

28

Sử dụng GNU Parallel bạn có thể làm:

parallel some_command ::: {1..1000}

Nếu bạn không muốn số đó làm đối số và chỉ chạy một công việc tại một thời điểm:

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

Xem video giới thiệu để được giới thiệu nhanh: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Xem qua hướng dẫn ( http://www.gnu.org/software/abul/abul_tutorial.html ). Bạn dòng lệnh với tình yêu bạn cho nó.


Tại sao bạn lại sử dụng một công cụ có lý do tồn tại, như được chứng minh rõ ràng bằng tên của nó, là để chạy mọi thứ song song , và sau đó đưa ra các lập luận khó hiểu -j1 -N0để tắt song song ????
Ross Presser

@RossPresser Đó là một câu hỏi rất hợp lý. Lý do là có thể dễ dàng hơn để diễn đạt những gì bạn muốn làm bằng cú pháp GNU Parallel. Hãy suy nghĩ: Làm thế nào bạn sẽ chạy some_command1000 lần nối tiếp mà không có đối số? Và: Bạn có thể diễn đạt ngắn hơn parallel -j1 -N0 some_command ::: {1..1000}không?
Ole Tange

Chà, tôi đoán bạn có một điểm, nhưng nó vẫn thực sự có cảm giác như sử dụng một khối động cơ được chế tạo tinh xảo như một cái búa. Nếu tôi có đủ động lực cảm thấy ân cần với những người kế vị tương lai của mình đang cố gắng hiểu những gì tôi đã làm, có lẽ tôi đã bọc sự nhầm lẫn trong một kịch bản shell và chỉ cần gọi nó với repeat.sh repeatcount some_command. Để thực hiện trong tập lệnh shell tôi có thể sử dụng parallel, nếu nó đã được cài đặt trên máy (có thể sẽ không được). Hoặc tôi có thể bị hút về xargs vì điều đó dường như là nhanh nhất khi không cần chuyển hướng some_command.
Ross Presser

Tôi xin lỗi nếu "câu hỏi rất hợp lý" của tôi được thể hiện một cách không hợp lý.
Ross Presser

1
Trong trường hợp cụ thể này có thể không quá rõ ràng. Sẽ rõ ràng hơn nếu bạn sử dụng nhiều tùy chọn GNU Parallel hơn, ví dụ: nếu bạn muốn thử lại các lệnh thất bại 4 lần và lưu kết quả đầu ra vào các tệp khác nhau:parallel -N0 -j1 --retries 4 --results outputdir/ some_command
Ole Tange

18

Một chức năng đơn giản trong tệp cấu hình bash ( ~/.bashrcthường) có thể hoạt động tốt.

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

Gọi nó như thế này.

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

1
Thích nó Bây giờ nếu nó có thể bị hủy bằng ctrl + c, điều đó thật tuyệt
slashdottir

Tại sao sử dụng cách này để lặp lại $ RANDOM n lần hiển thị cùng một số?
BladeMight

3
Điều này hoạt động hoàn hảo. Điều duy nhất tôi cần thay đổi là thay vì chỉ làm do ${*:2}tôi đã thêm eval do eval ${*:2}để đảm bảo rằng nó sẽ hoạt động để chạy các lệnh không bắt đầu với các tệp thực thi. Ví dụ: nếu bạn muốn đặt biến môi trường trước khi chạy lệnh như thế nào SOME_ENV=test echo 'test'.
5_nd_5

12

xargsnhanh :

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

Trên Linux 64-bit hiện đại, cung cấp:

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

Điều này có ý nghĩa, vì xargslệnh là một quá trình riêng duy nhất sinh ra /usr/bin/truelệnh nhiều lần, thay vì các vòng lặp forwhiletất cả được diễn giải trong Bash. Tất nhiên điều này chỉ hoạt động cho một lệnh duy nhất; nếu bạn cần thực hiện nhiều lệnh trong mỗi lần lặp vòng lặp, nó sẽ nhanh như vậy, hoặc có thể nhanh hơn, chuyển qua sh -c 'command1; command2; ...'xargs

Điều -P1này cũng có thể được thay đổi thành, -P8sinh ra 8 quá trình song song để có được một sự gia tăng lớn khác về tốc độ.

Tôi không biết tại sao GNU song song lại chậm như vậy. Tôi đã nghĩ rằng nó sẽ được so sánh với xargs.


1
GNU Parallel thực hiện khá nhiều thiết lập và ghi sổ: Nó hỗ trợ các chuỗi thay thế có thể lập trình, nó đệm đầu ra để đầu ra từ hai công việc song song không bao giờ bị lẫn lộn, nó hỗ trợ hướng (Bạn không thể làm: xargs "echo> foo") và vì nó không được viết bằng bash nó phải sinh ra một lớp vỏ mới để thực hiện chuyển hướng. Tuy nhiên, nguyên nhân chính là trong mô-đun Perl IPC :: open3 - vì vậy mọi công việc để có được điều đó nhanh hơn sẽ dẫn đến Song song GNU nhanh hơn.
Ole Tange


7

Đối với một, bạn có thể gói nó trong một chức năng:

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

Gọi nó như:

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

2
Điều này sẽ không hoạt động nếu lệnh chạy phức tạp hơn một lệnh đơn giản không có chuyển hướng.
chepner

Bạn có thể làm cho nó hoạt động cho các trường hợp phức tạp hơn, nhưng bạn có thể phải bọc lệnh trong một hàm hoặc tập lệnh.
bta

2
trđây là sai lệch và nó hoạt động trên đầu ra manytimes, không echo. Tôi nghĩ bạn nên loại bỏ nó khỏi mẫu.
Dmitry Ginzburg

7

xargs và seq sẽ giúp

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

quan điểm:

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

2
sự thay đổi và dấu gạch ngang kép trong lệnh làm gì? Là "sự thay đổi" thực sự cần thiết?
sebs

2
Điều này sẽ không hoạt động nếu lệnh phức tạp hơn một lệnh đơn giản không có chuyển hướng.
chepner

Rất chậm, tiếng vang $ RANDOM 50 lần mất 7 giây và thậm chí nó còn hiển thị cùng một số ngẫu nhiên mỗi lần chạy ...
BladeMight

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

Lưu ý gạch dưới thay vì sử dụng một biến.


9
Mặc dù về mặt kỹ thuật, _là một tên biến.
gniourf_gniourf

5

Nếu bạn ổn khi thực hiện định kỳ, bạn có thể chạy lệnh sau để chạy nó cứ sau 1 giây vô thời hạn. Bạn có thể đặt các kiểm tra tùy chỉnh khác tại chỗ để chạy nó n số lần.

watch -n 1 some_command

Nếu bạn muốn có xác nhận trực quan về các thay đổi, hãy thêm vào --differencestrướcls lệnh.

Theo trang người đàn ông OSX, cũng có

Tùy chọn --cumulation làm nổi bật "dính", hiển thị màn hình đang chạy của tất cả các vị trí đã từng thay đổi. Tùy chọn -t hoặc --no-title tắt tiêu đề hiển thị khoảng thời gian, lệnh và thời gian hiện tại ở đầu màn hình, cũng như dòng trống sau.

Trang người dùng Linux / Unix có thể được tìm thấy ở đây


5

Tôi đã giải quyết với vòng lặp này, trong đó lặp lại là một số nguyên biểu thị số của vòng lặp

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

4

Một câu trả lời khác: Sử dụng mở rộng tham số trên các tham số trống:

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

Đã thử nghiệm trên Centos 7 và MacOS.


Điều này thật thú vị, bạn có thể cung cấp thêm một số chi tiết về lý do tại sao điều này hoạt động?
Hassan Mahmud

1
Nó đang chạy mở rộng tham số n lần nhưng vì tham số trống nên nó không thực sự thay đổi những gì đang chạy
jcollum

Tìm kiếm mở rộng tham số và curl đã dẫn đến không có kết quả. Tôi hầu như không biết cuộn tròn. Sẽ thật tuyệt vời nếu bạn có thể chỉ cho tôi một số bài viết hoặc có lẽ là một tên phổ biến khác cho tính năng này. Cảm ơn.
Hassan Mahmud

1
Tôi nghĩ rằng điều này có thể giúp gnu.org/software/bash/manual/html_node/ Kẻ
jcollum

3

Tất cả các câu trả lời hiện có dường như đều yêu cầu bashvà không hoạt động với BSD UNIX tiêu chuẩn /bin/sh(ví dụ: kshtrên OpenBSD ).

Mã dưới đây sẽ hoạt động trên mọi 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

Một chút ngây thơ nhưng đây là những gì tôi thường nhớ trên đỉnh đầu:

for i in 1 2 3; do
  some commands
done

Rất giống với câu trả lời của @ joe-koberg. Của anh ấy tốt hơn đặc biệt nếu bạn cần nhiều lần lặp lại, chỉ khó hơn để tôi nhớ cú pháp khác bởi vì trong những năm qua tôi không sử dụng bashnhiều. Tôi có nghĩa là không cho kịch bản ít nhất.


1

Tệp 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

và đầu ra dưới đây

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


0

Đối với các vòng lặp có lẽ là cách đúng đắn để làm điều đó, nhưng đây là một cách thay thế thú vị:

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

Nếu bạn cần số lần lặp làm tham số cho lệnh gọi của mình, hãy sử dụng:

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

Chỉnh sửa: Đã nhận xét đúng rằng giải pháp đưa ra ở trên sẽ hoạt động trơn tru chỉ với các lệnh chạy đơn giản (không có đường ống, v.v.). bạn luôn có thể sử dụng mộtsh -c để làm những thứ phức tạp hơn, nhưng không đáng.

Một phương pháp khác tôi thường sử dụng là hàm sau:

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

bây giờ bạn có thể gọi nó là:

rep 3 10 echo iteration @

Hai số đầu tiên cho phạm vi. Các @sẽ nhận được dịch sang số lần lặp. Bây giờ bạn có thể sử dụng điều này với các đường ống quá:

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

với cung cấp cho bạn số lượng tệp trong thư mục R1 .. R10.


1
Điều này sẽ không hoạt động nếu lệnh phức tạp hơn một lệnh đơn giản không có chuyển hướng.
chepner

đủ công bằng, nhưng xem chỉnh sửa của tôi ở trên. Phương thức chỉnh sửa sử dụng sh -c, do đó cũng nên hoạt động với chuyển hướng.
stacksia

rep 1 2 touch "foo @"sẽ không tạo hai tệp "foo 1" và "foo 2"; thay vào đó, nó tạo ra ba tệp "foo", "1" và "2". Có thể có một cách để sử dụng quyền trích dẫn bằng cách sử dụng printf%q, nhưng sẽ rất khó để có được một chuỗi các từ tùy ý được trích dẫn chính xác thành một chuỗi duy nhất để chuyển đến sh -c.
chepner

OP yêu cầu ngắn gọn hơn , vậy tại sao bạn lại thêm một cái gì đó như thế này, khi đã có thêm 5 câu trả lời ngắn gọn?
zoska

Một khi bạn xác định định nghĩa reptrong của bạn .bashrc, các yêu cầu tiếp theo sẽ trở nên ngắn gọn hơn rất nhiều.
musiphil
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.