Есть ли лучший способ выполнить команду N раз в Bash?

304

Я иногда запускаю командную строку bash следующим образом:

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

Запускать some_commandнесколько раз подряд - 10 раз в этом случае.

Часто some_commandэто действительно цепочка команд или конвейер.

Есть ли более краткий способ сделать это?

bstpierre
источник
1
Помимо других хороших ответов, вы можете использовать let ++nвместо n=$((n+1))(на 3 символа меньше).
Musiphil
1
Некоторые признаки того, какие из этих методов являются переносимыми, были бы хорошими.
Фахим Митха
serverfault.com/questions/273238/…
Сиро Сантилли 法轮功 冠状 病 六四 事件 法轮功
3
Если вы готовы изменить снаряды, zshимеет repeat 10 do some_command; done.
Chepner
1
@JohnVandivier Я только что попробовал это в bash в свежем док-контейнере 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}что материализует строку со всеми этими целыми числами, разделенными пробелами.
Джо Коберг
@ Джо Коберг, спасибо за совет. Я обычно использую 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
BatchyX
источник
4
Это самый близкий путь к языку программирования ^^ - должен быть принят!
Nam G VU
Этот лучше, потому что это одна строка и, следовательно, легче использовать в терминале
Kunok
Вы также можете использовать переменную, например,x=10 for ((n=0; n < $x; n++));
Джелфи
@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
...

mitnk
источник
4
вам даже не нужна 1 (можно просто бежать seq 20)
Олег Васкевич
16
версия 2015:seq 20 | xargs -I{} echo "Hi there {}"
Mitnk
4
Обратите внимание, что zэто не очень хороший заполнитель, потому что он настолько распространен, что может быть обычным текстом в тексте команды. Использование {}будет лучше.
Mitnk
4
Любой способ отменить номер от seq? так что бы просто напечатать Hi there 20 раз (без номера)? РЕДАКТИРОВАТЬ: просто использовать, -I{}а затем не иметь {}в команде
Майк Граф
1
Просто используйте что-то, вы уверены, что это не будет в выводе, например, IIIIIтогда это выглядит хорошо, например:seq 20 | xargs -IIIIII timeout 10 yourCommandThatHangs
rubo77
79

Если вы используете оболочку zsh:

repeat 10 { echo 'Hello' }

Где 10 - количество повторений команды.

Уилсон Сильва
источник
11
Хотя это не ответ на вопрос (поскольку в нем явно упоминается bash), он очень полезен и помог мне решить мою проблему. +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_command1000 раз в сериале без аргументов? И: Вы можете выразить это короче parallel -j1 -N0 some_command ::: {1..1000}?
Оле Танге
Ну, я полагаю, у вас есть какая-то точка зрения, но все равно действительно хочется использовать точно обработанный блок двигателя в качестве молотка. Если бы я был достаточно мотивирован и чувствовал заботу о моих будущих преемниках, пытающихся понять, что я сделал, я бы, вероятно, обернул путаницу в сценарий оболочки и просто вызвал его repeat.sh repeatcount some_command. Для реализации в сценарии оболочки я мог бы использовать parallel, если он уже был установлен на машине (вероятно, не будет). Или я мог бы тяготеть к xargs, поскольку это происходит быстрее всего, когда перенаправление не требуется some_command.
Росс Прессер
Я прошу прощения, если мой «очень разумный вопрос» был выражен необоснованно.
Росс Прессер
1
В этом конкретном случае это может быть не так очевидно. Это становится более очевидным, если вы используете больше опций GNU Parallel, например, если вы хотите повторить неудачные команды 4 раза и сохранить вывод в разных файлах:parallel -N0 -j1 --retries 4 --results outputdir/ some_command
Ole Tange
18

Простая функция в конфигурационном файле bash ( ~/.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.
Оле Танге
11

Другая форма вашего примера:

n=0; while (( n++ < 10 )); do some_command; done
Приостановлено до дальнейшего уведомления.
источник
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
Это не будет работать, если команда для запуска является более сложной, чем простая команда без перенаправлений.
Чепнер
Вы можете заставить его работать в более сложных случаях, но вам, возможно, придется заключить команду в функцию или скрипт.
BTA
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
firejox
источник
2
что делает сдвиг и двойная черта в команде? Действительно ли необходим «сдвиг»?
SEBS
2
Это не будет работать, если команда более сложная, чем простая команда без перенаправлений.
Чепнер
Очень медленно, эхо $ RANDOM 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
fvlgnn
источник
4

Еще один ответ: используйте расширение параметров для пустых параметров:

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

Протестировано на Centos 7 и MacOS.

jcollum
источник
Это интересно, можете ли вы предоставить более подробную информацию о том, почему это работает?
Хасан Махмуд
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
$
CNST
источник
2

Немного наивно, но это то, что я обычно вспоминаю на макушке:

for i in 1 2 3; do
  some commands
done

Очень похоже на ответ @ Джо-Коберга. Его лучше, особенно если вам нужно много повторений, мне сложнее запомнить другой синтаксис, потому что в последние годы я не bashочень часто его использую . Я имею в виду не для сценариев по крайней мере.

akostadinov
источник
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$
upog
источник
0

Как насчет альтернативной формы, forупомянутой в (bashref) Looping Constructs ?

Самба
источник
4
бросить ему кость и привести пример альтернативной формы?
Джо Коберг
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.

stacksia
источник
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