Чи є кращий спосіб запустити команду N разів у bash?


304

Я час від часу запускаю командний рядок bash:

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

Бігати some_commandкілька разів поспіль - у цьому випадку 10 разів.

Часто some_commandце дійсно ланцюжок команд або трубопровід.

Чи є більш стислий спосіб це зробити?


1
Крім інших приємних відповідей, ви можете використовувати let ++nзамість n=$((n+1))(3 менше символів).
musiphil

1
Деякі вказівки того, який із цих методів є портативним, було б непогано.
Faheem Mitha


3
Якщо ви готові змінити снаряди, zshмає repeat 10 do some_command; done.
чепнер

1
@JohnVandivier Я щойно спробував це в баші в свіжому контейнері докера 18.04, оригінальний синтаксис, як розміщено в моєму запитанні, все ще працює. Можливо, ти використовуєш оболонку, відмінну від bash, як / bin / sh, що насправді тире, і в такому випадку я повертаюся назад sh: 1: [[: not found.
bstpierre

Відповіді:


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

Або як однорозмір для тих, хто хоче легко скопіювати та вставити:

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

19
Якщо у вас багато ітерацій, форма з for (n=0;n<k;n++))може бути кращою; Я підозрюю {1..k}, що матеріалізує рядок із усіма цілими числами, розділеними пробілами.
Джо Коберг

@Joe Koberg, дякую за пораду. Я зазвичай використовую N <100, тому це здається гарним.
bstpierre

10
@bstpierre: Форма розширення дужок не може використовувати змінні (легко) для визначення діапазону в Bash.
Призупинено до подальшого повідомлення.

5
Це правда. Розширення дужки виконується перед змінним розширенням відповідно до gnu.org/software/bash/manual/bashref.html#Brace-Expansion , таким чином воно ніколи не побачить значень будь-яких змінних.
Джо Коберг

5
Сумна річ про змінне розширення. Я використовую наступне для циклу n-разів і відформатував номери: 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

Використання константи:

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

Використання змінної (може включати математичні вирази):

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

4
Це найближчий шлях до мови програмування ^^ - має бути прийнятим!
Нам Г ВУ

Цей кращий, тому що це
однорядний

Ви також можете використовувати змінну, наприкладx=10 for ((n=0; n < $x; n++));
Jelphy

@Kunok Прийняту відповідь так само прийнятно виконувати в одному рядку:for run in {1..10}; do echo "on run $run"; done
Дуглас Адамс

що робити, якщо nвже встановлено певне значення? for (( ; n<10; n++ ))не працює / редагує: найкраще скористайтеся однією з інших відповідей, як while (( n++...одна
phil294

121

Ще один простий спосіб зламати його:

seq 20 | xargs -Iz echo "Hi there"

запустити відлуння 20 разів.


Зауважте, що seq 20 | xargs -Iz echo "Hi there z"вийде:

Привіт, там 1
Привіт, там 2
...


4
вам навіть не потрібен 1 (можна просто запустити seq 20)
Олег Васкевич

16
версія 2015:seq 20 | xargs -I{} echo "Hi there {}"
mitnk

4
Зауважте, що zце не є хорошим заповнювачем, тому що воно настільки поширене, що може бути звичайним текстом у тексті команди. Використовувати {}буде краще.
mitnk

4
Будь-який спосіб відкинути номер seq? тож він би просто надрукував Hi there 20 разів (без номера)? EDIT: просто використовуйте, -I{}а потім не майте жодної {}команди
Mike Graf

1
Просто використовуйте щось, ви впевнені, що його не буде у виході, наприклад, IIIIIтоді це виглядає приємно, наприклад:seq 20 | xargs -IIIIII timeout 10 yourCommandThatHangs
rubo77

79

Якщо ви використовуєте оболонку zsh:

repeat 10 { echo 'Hello' }

Де 10 - кількість повторень команди.


11
Хоча це не відповідь на питання (оскільки він прямо згадує баш), це дуже корисно і допомогло мені вирішити свою проблему. +1
OutOfBound

2
Крім того, якщо ви просто набрали його в терміналі, ви можете використовувати щось на кшталтrepeat 10 { !! }
Ренато Гомес

28

За допомогою GNU Parallel ви можете:

parallel some_command ::: {1..1000}

Якщо ви не хочете, щоб число було аргументом, а запускайте лише одне завдання за один раз:

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

Перегляньте вступне відео для швидкого вступу: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Пройдіться по підручнику ( http://www.gnu.org/software/parallel/parallel_tutorial.html ). Ти командний рядок з любов'ю тебе за це.


Чому б ви використовували інструмент, сама причина існування якого, як явно підтверджено його ім'ям, полягає в тому, щоб запускати речі паралельно , а потім давати йому виразні аргументи, -j1 -N0які вимикають паралелізм ????
Росс Пресер

@RossPresser Це дуже розумне питання. Причина в тому, що може бути простіше виразити те, що ви хочете зробити, використовуючи синтаксис GNU Parallel. Подумайте: як би ви some_commandпослідовно запускали 1000 разів без аргументів? І: Ви можете висловити це коротше, ніж parallel -j1 -N0 some_command ::: {1..1000}?
Оле Танге

Ну, напевно, у вас є якась точка, але все одно справді відчувається, як використовувати чудовий блок двигуна як молот. Якби я був достатньо вмотивований і почував себе уважним до своїх майбутніх наступників, які намагалися зрозуміти, що я зробив, я, мабуть, загорнув би плутанину в сценарій оболонки і просто подзвонив би repeat.sh repeatcount some_command. Для реалізації в скрипті оболонки я міг би використовувати parallel, якби він вже був встановлений на машині (напевно, не було б). Або я можу тяжіти до xargs, оскільки це здається найшвидшим, коли перенаправлення не потрібно some_command.
Росс Пресер

Прошу вибачення, якщо моє "дуже розумне запитання" було висловлене необгрунтовано.
Росс Пресер

1
У цьому конкретному випадку це може бути не настільки очевидно. Це стає більш очевидним, якщо ви використовуєте більше опцій паралельної програми GNU, наприклад, якщо ви хочете повторити невдалі команди 4 рази та зберегти вихід у різні файли:parallel -N0 -j1 --retries 4 --results outputdir/ some_command
Ole Tange,

18

Проста функція у файлі bash config ( ~/.bashrcчасто) може добре працювати.

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

Назвіть це так.

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

1
Люблю це. Тепер, якщо його можна скасувати за допомогою ctrl + c, це було б чудово
slashdottir

Чому за допомогою цього способу для відлучення $ RANDOM n разів відображається однакове число?
BladeMight

3
Це прекрасно працює. Єдине, що мені потрібно було змінити, це замість того, щоб просто робити, do ${*:2}я додав eval, do eval ${*:2}щоб переконатися, що він буде працювати для запуску команд, які не починаються із виконуваних файлів. Наприклад, якщо ви хочете встановити змінну середовища перед запуском команди типу SOME_ENV=test echo 'test'.
5_nd_5

12

xargsце швидко :

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

На сучасному 64-бітному Linux дає:

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

Це має сенс, оскільки xargsкоманда - це єдиний власний процес, який породжує /usr/bin/trueкоманду кілька разів, замість forі whileциклів, які всі інтерпретуються в Bash. Звичайно, це працює лише для однієї команди; якщо вам потрібно зробити кілька команд у кожній циклі ітерації, це буде так само швидко, а може, і швидше, ніж перехід sh -c 'command1; command2; ...'до xargs

-P1Також може бути змінений, скажімо, -P8на нерест 8 процесів паралельно , щоб отримати ще один великий приріст в швидкості.

Я не знаю, чому паралель GNU так повільний. Я би подумав, що це можна порівняти з xargs.


1
GNU Parallel робить досить багато параметрів налаштування та ведення бухгалтерського обліку: він підтримує програмовані рядки заміни, він буферизує вихід, тому вихід з двох паралельних завдань ніколи не змішується, він підтримує напрямок (не можна робити: xargs "echo> foo"), і оскільки це не написано в bash, він повинен породити нову оболонку, щоб зробити перенаправлення. Основна причина, однак, полягає в модулі Perl IPC :: open3 - тому будь-яка робота над його швидкістю призведе до більш швидкої паралельності GNU.
Оле Танге


7

Для одного ви можете згорнути його у функції:

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

Назвіть це так:

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

2
Це не спрацює, якщо команда для запуску є складнішою, ніж проста команда без перенаправлень.
чепнер

Ви можете змусити його працювати для більш складних випадків, але, можливо, вам доведеться загортати команду у функцію чи сценарій.
бта

2
trТут вводить в оману , і він працює на виході manytimes, а НЕ echo. Я думаю, ви повинні вилучити його із зразка.
Дмитро Гінзбург

7

xargs і seq допоможуть

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

вид:

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

2
що робить зсув і подвійний тире в команді? Чи справді потрібна «зміна»?
sebs

2
Це не спрацює, якщо команда складніша за просту команду без перенаправлень.
чепнер

Дуже повільно, відлуння $ РАНДОМ 50 разів зайняло 7 секунд, і навіть тому воно показує однакове випадкове число кожного пробігу ...
BladeMight

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

Зверніть увагу на підкреслення замість використання змінної.


9
Хоча технічно _це ім'я змінної.
gniourf_gniourf

5

Якщо вам все вдається робити це періодично, ви можете запустити наступну команду, щоб виконувати її кожні 1 сек нескінченно. Ви можете встановити інші спеціальні чеки, щоб запустити їх n кількість разів.

watch -n 1 some_command

Якщо ви бажаєте отримати візуальне підтвердження змін, додайте --differencesдо цьогоls команди.

Відповідно до сторінки користувача OSX, також є

Параметр --cumulative робить підсвічування "липкою", представляючи працюючий дисплей усіх позицій, які коли-небудь змінювалися. Параметр -t або --no title вимикає заголовок, що відображає інтервал, команду та поточний час у верхній частині екрана, а також наступний порожній рядок.

Linux / Unix сторінка людини можна знайти тут


5

Я вирішив цю петлю, де повтор - це ціле число, яке представляє число циклів

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

4

Ще одна відповідь: Використовуйте розширення параметрів на порожніх параметрах:

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

Тестували на Centos 7 та MacOS.


Це цікаво, чи можете ви надати ще детальну інформацію про те, чому це працює?
Хасан Махмуд

1
Він виконує розширення параметра n разів, але оскільки параметр порожній, він фактично не змінює те, що виконується
jcollum

Пошук розширення та згортання параметрів не призвів до результатів. Я ледве знаю завиток. Було б дивним, якби ви могли вказати мені на якусь статтю чи, можливо, іншу загальну назву цієї функції. Дякую.
Хасан Махмуд

1
Я думаю, що це може допомогти gnu.org/software/bash/manual/html_node/…
jcollum

3

Усі існуючі відповіді, як видається, вимагають bashі не працюють зі стандартним BSD UNIX /bin/sh(наприклад, kshна OpenBSD ).

Наведений нижче код повинен працювати на будь-якому 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

Трохи наївно, але саме це я зазвичай пам’ятаю з верху голови:

for i in 1 2 3; do
  some commands
done

Дуже схожа на відповідь @ joe-koberg. Його краще, особливо якщо вам потрібно багато повторень, просто важче мені запам’ятати інші синтаксиси, оскільки в останні роки я не дуже використовую bash. Я маю на увазі не для сценаріїв принаймні.


1

Файл сценарію

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

echo "The argument is  arg: $1"

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

і вихід нижче

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


0

Для петель це, мабуть, правильний спосіб зробити це, але ось цікава альтернатива:

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

Якщо вам потрібен номер ітерації як параметр для виклику, використовуйте:

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

Редагувати: Справедливо прокоментували, що рішення, подане вище, буде працювати безперебійно лише з простими командними прогонами (без труб і т.д.). ви завжди можете використовуватиsh -c складні речі, але не варто.

Іншим методом, який я зазвичай використовую, є наступна функція:

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

тепер ви можете назвати це як:

rep 3 10 echo iteration @

Перші два числа дають діапазон. Заповіт @буде переведено на номер ітерації. Тепер ви можете використовувати це і для труб:

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

з надаємо вам кількість файлів у каталогах R1 .. R10.


1
Це не спрацює, якщо команда складніша за просту команду без перенаправлень.
чепнер

досить справедливо, але дивіться мою редакцію вище. Відредагований метод використовує sh -c, тому слід працювати і з перенаправленням.
stacksia

rep 1 2 touch "foo @"не створить два файли "foo 1" і "foo 2"; скоріше, він створює три файли "foo", "1" та "2". Там може бути способом , щоб отримати право квотування , використовуючи printfі %q, але це буде складно отримати довільну послідовність слів правильно цитованих в один рядок , щоб перейти до sh -c.
чепнер

ОП попросили більш стисло , то чому ви додаєте щось подібне, коли вже є ще 5 стислих відповідей?
zoska

Після того, як ви визначите своє визначення repу вашому .bashrc, подальший виклик стане набагато більш стислим.
musiphil
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.