Повторять команду Unix каждые x секунд навсегда
Существует встроенная команда Unix repeat
, первым аргументом которой является количество повторений команды, где команда (с любыми аргументами) указана оставшимися аргументами в repeat
.
Например,
% repeat 100 echo "I will not automate this punishment."
будет повторять данную строку 100 раз, а затем останавливаться.
Мне нужна аналогичная команда - назовем ее forever
â € ", которая работает аналогично, кроме первого аргумента, - это количество секунд для паузы между повторами, и оно повторяется навсегда. Например,
% forever 5 echo "This will get echoed every 5 seconds forever and ever."
Я думал, что спрошу, существует ли такая вещь, прежде чем я ее напишу. Я знаю, что это похоже на двухстрочный скрипт Perl или Python, но, возможно, есть более стандартный способ сделать это. Если нет, не стесняйтесь размещать решение на вашем любимом языке сценариев, стиль Rosetta Stone .
PS: Может быть, лучшим способом сделать это было бы обобщение repeat
, чтобы взять как количество раз для повторения (с -1 значение бесконечности), так и количество секунд, чтобы спать между повторами.
Вышеприведенные примеры затем станут:
% repeat 100 0 echo "I will not automate this punishment."
% repeat -1 5 echo "This will get echoed every 5 seconds forever."
25 ответов
Попробуйте выполнить команду watch
.
Usage: watch [-dhntv] [--differences[=cumulative]] [--help] [--interval=<n>]
[--no-title] [--version] <command>`
Итак, чтобы:
watch -n1 command
будет запускать команду каждую секунду (ну, технически, каждую секунду плюс время, которое требуется для команды command
для запуска в виде watch
(по крайней мере, procps
и busybox
) просто наступает одна секунда между двумя запусками команды command
) навсегда.
Вы хотите передать команду exec
вместо sh -c
, используйте опцию -x
:
watch -n1 -x command
В macOS вы можете получить watch
из Mac-портов :
port install watch
Или вы можете получить его из Homebrew :
brew install watch
Bash
while
+ sleep
:
while true
do
echo "Hi"
sleep 1
done
Вот то же самое, что и сокращенный однострочный (из комментариев ниже):
while sleep 1; do echo "Hi"; done
Использует ;
для разделения команд и использует sleep 1
для теста while
, так как он всегда возвращает true. Вы можете добавить больше команд в цикл - просто отделите их с помощью ;
Это просто более короткая версия других ответов while+sleep
, если вы часто выполняете такие задачи как свою повседневную жизнь, используя это, избавляя вас от ненужных нажатий клавиш, и если ваша команда линия начинает понимать, что это немного легче. Но сначала это начинается со спящего.
Это обычно полезно, если вам нужно следовать чему-то, имеет однострочный вывод, такой как загрузка машины:
while sleep 1; do uptime; done
Одна из проблем, с которой до сих пор звучат все ответы, заключается в том, что время выполнения команды может дрейфовать. Например, если вы выполняете команду sleep 10
между командами, и для выполнения этой команды требуется 2 секунды, она будет запускаться каждые 12 секунд; если требуется много времени для запуска, то в долгосрочной перспективе время его запуска может быть непредсказуемым.
Это может быть именно то, что вы хотите; если это так, используйте одно из других решений или используйте этот, но упростите вызов sleep
.
Для одноминутного разрешения задания cron будут выполняться в указанное время независимо от того, сколько времени занимает каждая команда. (Фактически новое задание cron будет запущено, даже если предыдущий все еще работает.)
Вот простой скрипт Perl, который засыпает до следующего интервала, например, с интервалом в 10 секунд команда может выполняться в 12:34:00, 12:34:10, 12:34:20 и т. д. даже если сама команда занимает несколько секунд. Если команда запускает более interval
секунд, следующий интервал будет пропущен (в отличие от cron). Интервал вычисляется относительно эпохи, поэтому интервал в 86400 секунд (1 день) будет выполняться в полночь UTC.
#!/usr/bin/perl
use strict;
use warnings;
if (scalar @ARGV < 2) {
die "Usage: $0 seconds command [args...]\n";
}
$| = 1; # Ensure output appears
my($interval, @command) = @ARGV;
# print ">>> interval=$interval command=(@command)\n";
while (1) {
print "sleep ", $interval - time % $interval, "\n";
sleep $interval - time % $interval;
system @command; # TODO: Handle errors (how?)
}
Я думаю, что весь ответ здесь до сих пор слишком запутан, или вместо этого отвечает на другой вопрос:
- "Как многократно запускать программу, чтобы задержка между X секундами была завершена, а следующая -" .
Реальный вопрос:
- «Как запустить программу каждые X секунд»
Это две разные вещи, когда команде требуется время для завершения.
Возьмем, к примеру, скрипт foo.sh
(притвориться, что это программа, для завершения которой требуется несколько секунд).
#!/bin/bash
# foo.sh
echo `date +"%H:%M:%S"` >> output.txt;
sleep 2.5;
# ---
Вы хотите запускать это каждую секунду, и большинство из них предложит watch -n1 ./foo.sh
или while sleep 1; do ./foo.sh; done
. Однако это дает результат:
15:21:20
15:21:23
15:21:27
15:21:30
15:21:34
Это не выполняется каждую секунду. Даже с флагом -p
, поскольку предлагаемая страница man watch
может решить эту проблему, результат будет таким же.
Простой способ выполнить желаемую задачу, к которой нужно прикоснуться, - запустить команду в фоновом режиме. Другими словами:
while sleep 1; do (./foo.sh &) ; done
И это все, что есть.
Вы можете запускать его каждые 500 мс с помощью sleep 0.5
или того, что у вас есть.
В bash
:
bash -c 'while [ 0 ]; do echo "I will not automate this punishment in absurdum."; done'
(echo
может быть заменен любой командой ...
Или в perl
:
perl -e 'for (;1;) {print "I will not automate this punishment in absurdum.\n"}'
Где print "I will not automate this punishment in absurdum.\n"
может быть заменен на «любую» команду, окруженную обратными окнами (`).
И для паузы добавьте оператор sleep
внутри цикла for:
bash -c 'while [ 0 ]; do echo "I will not automate this punishment in absurdum."; sleep 1; done'
и
perl -e 'for (;1;) {print "I will not automate this punishment in absurdum.\n"; sleep 1}'
Недавний bash> = 4.2 в соответствии с последним ответом на ядро Linux.
Чтобы ограничить время выполнения, есть без forks ! Используются только встроенные.
Для этого я использую read
встроенную функцию вместо sleep
. К сожалению, это не будет работать с сеансами notty .
Быстрый bash function "repeat
"по запросу:
repeat () {
local repeat_times=$1 repeat_delay=$2 repeat_foo repeat_sleep
read -t .0001 repeat_foo
if [ $? = 1 ] ;then
repeat_sleep() { sleep $1 ;}
else
repeat_sleep() { read -t $1 repeat_foo; }
fi
shift 2
while ((repeat_times)); do
((repeat_times=repeat_times>0?repeat_times-1:repeat_times))
"${@}"
((repeat_times))&& ((10#${repeat_delay//.})) &&
repeat_sleep $repeat_delay
done
}
Маленький тест с цитируемыми строками:
repeat 3 0 printf "Now: %(%T)T, Hello %s.\n" -1 Guy
Now: 15:13:43, Hello Guy.
Now: 15:13:43, Hello Guy.
Now: 15:13:43, Hello Guy.
repeat -1 .5 printf "Now: %(%T)T, Hello %s.\n" -1 Guy
Now: 15:14:14, Hello Guy.
Now: 15:14:14, Hello Guy.
Now: 15:14:15, Hello Guy.
Now: 15:14:15, Hello Guy.
Now: 15:14:16, Hello Guy.
Now: 15:14:16, Hello Guy.
Now: 15:14:17, Hello Guy.
Now: 15:14:17, Hello Guy.
Now: 15:14:18, Hello Guy.
Now: 15:14:18, Hello Guy.
^C
В зависимости от детализации и продолжительности поданной команды ...
В последних ядрах Linux есть файл procfile /proc/timer_list
, содержащий информацию о времени в наносекундах.
Если вы хотите запускать команду ровно один раз в секунду, ваша команда должна заканчиваться менее чем за секунду! И , вы должны sleep
только rest текущей секунды.
Если задержка важнее, и ваша команда не требует значительного времени, вы можете:
command=(echo 'Hello world.')
delay=10
while :;do
printf -v now "%(%s)T" -1
read -t $(( delay-(now%delay) )) foo
${command[@]}
done.
Но если ваша цель - получить более тонкую детализацию, вы должны:
Используйте информацию о наносекундах, чтобы подождать до начала второй ...
Для этого я написал небольшую функцию bash:
# bash source file for nano wait-until-next-second
mapfile </proc/timer_list _timer_list
for ((_i=0;_i<${#_timer_list[@]};_i++));do
((_c+=${#_timer_list[_i]}))
[[ ${_timer_list[_i]} =~ ^now ]] && TIMER_LIST_READ=$_c
[[ ${_timer_list[_i]} =~ offset:.*[1-9] ]] && \
TIMER_LIST_OFFSET=${_timer_list[_i]//[a-z.: ]} && \
break
done
unset _i _timer_list _c
readonly TIMER_LIST_OFFSET TIMER_LIST_READ
waitNextSecondHires() {
local nsnow nsslp
read -N$TIMER_LIST_READ nsnow </proc/timer_list
nsnow=${nsnow%% nsecs*}
nsnow=$((${nsnow##* }+TIMER_LIST_OFFSET))
nsslp=$((2000000000-10#${nsnow:${#nsnow}-9}))
read -t .${nsslp:1} foo
}
После поиска их можно:
command=(echo 'Hello world.')
while :;do
waitNextSecondHires
${command[@]}
done.
запустите ${command[@]}
непосредственно в командной строке, чем сравните с
command=(eval "echo 'Hello world.';sleep .3")
while :;do
waitNextSecondHires
${command[@]}
done.
это должно дать точно такой же результат.
Нанимает bash function "repeat
"по запросу:
Вы можете указать это:
mapfile </proc/timer_list _timer_list
for ((_i=0;_i<${#_timer_list[@]};_i++));do
((_c+=${#_timer_list[_i]}))
[[ ${_timer_list[_i]} =~ ^now ]] && TIMER_LIST_READ=$_c
[[ ${_timer_list[_i]} =~ offset:.*[1-9] ]] && \
TIMER_LIST_OFFSET=${_timer_list[_i]//[a-z.: ]} && \
break
done
unset _i _timer_list _c
readonly TIMER_LIST_OFFSET TIMER_LIST_READ
repeat_hires () {
local repeat_times=$1 repeat_delay=$2 repeat_foo repeat_sleep repeat_count
read -t .0001 repeat_foo
if [ $? = 1 ] ;then
repeat_sleep() { sleep $1 ;}
else
repeat_sleep() { read -t $1 repeat_foo; }
fi
shift 2
printf -v repeat_delay "%.9f" $repeat_delay
repeat_delay=${repeat_delay//.}
read -N$TIMER_LIST_READ nsnow </proc/timer_list
nsnow=${nsnow%% nsec*}
started=${nsnow##* }
while ((repeat_times)); do
((repeat_times=repeat_times>0?repeat_times-1:repeat_times))
"${@}"
((repeat_times)) && ((10#$repeat_delay)) && {
read -N$TIMER_LIST_READ nsnow </proc/timer_list
nsnow=${nsnow%% nsec*}
nsnow=${nsnow##* }
(( (nsnow - started) / 10#$repeat_delay - repeat_count++ )) &&
printf >&2 "WARNING: Command '%s' too long for %f delay.\n" \
"${*}" ${repeat_delay:0:${#repeat_delay}-9
}.${repeat_delay:${#repeat_delay}-9}
printf -v sleep "%010d" $((
10#$repeat_delay - ( ( nsnow - started ) % 10#$repeat_delay ) ))
repeat_sleep ${sleep:0:${#sleep}-9}.${sleep:${#sleep}-9}
}
done
}
Затем попробуйте:
time repeat_hires 21 .05 sh -c 'date +%s.%N;sleep .01'
1480867565.152022457
1480867565.201249108
1480867565.251333284
1480867565.301224905
1480867565.351236725
1480867565.400930482
1480867565.451207075
1480867565.501212329
1480867565.550927738
1480867565.601199721
1480867565.651500618
1480867565.700889792
1480867565.750963074
1480867565.800987954
1480867565.853671458
1480867565.901232296
1480867565.951171898
1480867566.000917199
1480867566.050942638
1480867566.101171249
1480867566.150913407
real 0m1.013s
user 0m0.000s
sys 0m0.016s
time repeat_hires 3 .05 sh -c 'date +%s.%N;sleep .05'
1480867635.380561067
WARNING: Command 'sh -c date +%s.%N;sleep .05' too long for 0.050000 delay.
1480867635.486503367
WARNING: Command 'sh -c date +%s.%N;sleep .05' too long for 0.050000 delay.
1480867635.582332617
real 0m0.257s
user 0m0.000s
sys 0m0.004s
У вас есть возможность попробовать это (Bash)?
forever () {
TIMES=shift;
SLEEP=shift;
if [ "$TIMES" = "-1" ]; then
while true;
do
[email protected]
sleep $SLEEP
done
else
repeat "$TIMES" [email protected]
fi; }
Perl
#!/usr/bin/env perl
# First argument is number of seconds to sleep between repeats, remaining
# arguments give the command to repeat forever.
$sleep = shift;
$cmd = join(' ', @ARGV);
while(1) {
system($cmd);
sleep($sleep);
}
Bash и некоторые из его родственных оболочек имеют удобную нотацию (( ... ))
, в которой могут быть оценены арифметические выражения.
Итак, как ответ на ваш третий вызов, где и счет повторения, и задержка между каждым повторением должны быть настраиваемыми, вот один из способов сделать это:
repeat=10
delay=1
i=0
while (( i++ < repeat )); do
echo Repetition $i
sleep $delay
done
Этот ответ также страдает от временного дрейфа, охватываемого ответом Keith's .
Как упоминалось gbrandt, если доступна команда watch
, обязательно используйте ее. Однако некоторые Unix-системы не устанавливают его по умолчанию (по крайней мере, они не работают там).
Вот еще одно решение с немного отличающимся синтаксисом и выходом (работает в BASH и SH):
while [ 1 ] ; do
<cmd>
sleep <x>
echo ">>>>>>>>>>>>>" `date` ">>>>>>>>>>>>>>"
done
Изменить: я удалил некоторые "." в последнем выражении эха ... holdover из моих дней Perl;)
Если ваше намерение состоит не в том, чтобы отображать сообщение на вашем экране, и если вы могли позволить себе повторить работу с точки зрения минут, crontab , возможно, станет вашим лучшим инструментом. Например, если вы хотите выполнять свою команду каждую минуту, вы должны написать что-то вроде этого в файле crontab
:
* * * * * my_precious_command
Пожалуйста, ознакомьтесь с учебником для дальнейшего примера. Кроме того, вы можете легко установить тайминги, используя генератор кода Crontab .
Что, если бы у нас были оба?
Вот идея: только с «интервалом» она повторяется навсегда. С «интервалом» и «раз» он повторяет это количество раз, разделенное «интервалом».
Использование:
$ loop [interval [times]] command
Итак, алгоритм будет:
- Элемент списка
- если $ 1 содержит только цифры, это интервал (по умолчанию 2)
- если $ 2 содержит только цифры, это количество раз (по умолчанию бесконечно)
- while цикл с этими параметрами
- спящий интервал
- Если задано несколько раз, уменьшите значение var до тех пор, пока оно не будет достигнуто
Следовательно:
loop() {
local i=2 t=1 cond
[ -z ${1//[0-9]/} ] && i=$1 && shift
[ -z ${1//[0-9]/} ] && t=$1 && shift && cond=1
while [ $t -gt 0 ]; do
sleep $i
[ $cond ] && : $[--t]
[email protected]
done
}
Я закончил создание варианта ответа свалога. С его помощью вам пришлось ждать X секунд для первой итерации, я также запускаю мой на переднем плане, поэтому ..
./foo.sh;while sleep 1; do (./foo.sh) ; done
Быстрая, грязная и, вероятно, опасная для загрузки, но если вы предприимчивы и знаете, что делаете, поместите ее в repeat.sh
и chmod 755
it ,
while true
do
eval $1
sleep $2
done
Вызвать его с помощью ./repeat.sh <command> <interval>
В моем здравом смысле говорится, что это, вероятно, злой способ сделать это, верно ли мое здравое чувство?
Вы можете запустить скрипт из init (добавив строку в /etc /inittab). Этот скрипт должен запускать вашу команду, спя, пока вы хотите подождать, пока не запустите сценарий еще раз, и выйдите из него. Инициалист снова запустит ваш скрипт после выхода.
Легкий способ повторить задание от crontab с интервалом менее одной минуты (пример 20 секунд):
кронтаб: * * * * * script.sh
script.sh:
#!/bin/bash
>>type your commands here.
sleep 20
>>retype your commands here.
sleep 20
>>retype your commands here.
Вы можете получить мощность от интерпретатора python из оболочки.
python3 -c "import time, os
while True:
os.system('your command')
time.sleep(5)
заменить пять в любое время (сек), которое вам нравится.
Внимание: возврат возвращает SHIFT + ВВОД, чтобы возвращать ввод в оболочку. И не забудьте ввести 4 строки SPACE в каждой строке в цикле while.
Это решение работает в MacOSX 10.7. Он прекрасно работает.
bash -c 'while [ 0 ]; do \
echo "I will not automate this punishment in absurdum."; done'
В моем случае
bash -c 'while [ 0 ]; do ls; done'
или
bash -c 'while [ 0 ]; do mv "Desktop/* Documents/Cleanup"; done'
для постоянной очистки рабочего стола.
Вы можете указать эту рекурсивную функцию:
#!/bin/bash
ininterval () {
delay=$1
shift
$*
sleep $delay
ininterval $delay $*
}
или добавьте:
ininterval $*
и вызовите скрипт.
Чтобы повторно запустить команду в окне консоли, я обычно запускаю что-то вроде этого:
while true; do (run command here); done
Это работает и для нескольких команд, например, для отображения постоянного обновления часов в окне консоли:
while true; do clear; date; sleep 1; done
... Интересно, сколько сложных решений может быть создано при решении этой проблемы.
Это может быть так просто ...
open /etc/crontab
поместите туда 1 следующую строку в конец файла, например:
*/NumberOfSeconds * * * * user /path/to/file.sh
Если вы хотите запускать что-то каждые 1 секунду, просто поставьте туда:
*/60 * * * * root /path/to/file.sh
where that file.sh could be chmod 750 /path/to/file.sh
и внутри этого файла.sh должно быть:
#!/bin/bash
#What does it do
#What is it runned by
your code or commands
и все!
НАСЛАЖДАЙТЕСЬ!
#! /bin/sh
# Run all programs in a directory in parallel
# Usage: run-parallel directory delay
# Copyright 2013 by Marc Perkel
# docs at http://wiki.junkemailfilter.com/index.php/How_to_run_a_Linux_script_every_few_seconds_under_cron"
# Free to use with attribution
if [ $# -eq 0 ]
then
echo
echo "run-parallel by Marc Perkel"
echo
echo "This program is used to run all programs in a directory in parallel"
echo "or to rerun them every X seconds for one minute."
echo "Think of this program as cron with seconds resolution."
echo
echo "Usage: run-parallel [directory] [delay]"
echo
echo "Examples:"
echo " run-parallel /etc/cron.20sec 20"
echo " run-parallel 20"
echo " # Runs all executable files in /etc/cron.20sec every 20 seconds or 3 times a minute."
echo
echo "If delay parameter is missing it runs everything once and exits."
echo "If only delay is passed then the directory /etc/cron.[delay]sec is assumed."
echo
echo 'if "cronsec" is passed then it runs all of these delays 2 3 4 5 6 10 12 15 20 30'
echo "resulting in 30 20 15 12 10 6 5 4 3 2 executions per minute."
echo
exit
fi
# If "cronsec" is passed as a parameter then run all the delays in parallel
if [ $1 = cronsec ]
then
$0 2 &
$0 3 &
$0 4 &
$0 5 &
$0 6 &
$0 10 &
$0 12 &
$0 15 &
$0 20 &
$0 30 &
exit
fi
# Set the directory to first prameter and delay to second parameter
dir=$1
delay=$2
# If only parameter is 2,3,4,5,6,10,12,15,20,30 then automatically calculate
# the standard directory name /etc/cron.[delay]sec
if [[ "$1" =~ ^(2|3|4|5|6|10|12|15|20|30)$ ]]
then
dir="/etc/cron.$1sec"
delay=$1
fi
# Exit if directory doesn't exist or has no files
if [ ! "$(ls -A $dir/)" ]
then
exit
fi
# Sleep if both $delay and $counter are set
if [ ! -z $delay ] && [ ! -z $counter ]
then
sleep $delay
fi
# Set counter to 0 if not set
if [ -z $counter ]
then
counter=0
fi
# Run all the programs in the directory in parallel
# Use of timeout ensures that the processes are killed if they run too long
for program in $dir/* ; do
if [ -x $program ]
then
if [ "0$delay" -gt 1 ]
then
timeout $delay $program &> /dev/null &
else
$program &> /dev/null &
fi
fi
done
# If delay not set then we're done
if [ -z $delay ]
then
exit
fi
# Add delay to counter
counter=$(( $counter + $delay ))
# If minute is not up - call self recursively
if [ $counter -lt 60 ]
then
. $0 $dir $delay &
fi
# Otherwise we're done
С помощью zsh
и sleep
, которая принимает аргументы с плавающей запятой:
typeset -F SECONDS=0 n=0
repeat 100 {cmd; sleep $(((n+=3) - SECONDS))}
Или для forever
:
for ((;;)) {cmd; sleep $(((n+=3) - SECONDS))}
Если ваш sleep
не поддерживает float, вы всегда можете переопределить его как оболочку вокруг zsh
zselect
builtin: р>
zmodload zsh/zselect
sleep() zselect -t $((($1 * 100) | 0))
until ! sleep 60; do echo $(date); command; command; command; done
работает для меня.
- Мне это не нужно ровно через каждые 60 секунд.
- «смотреть» не выполняет команды во всех системах.
- «watch» может принимать только одну команду (или файл сценария)
- включение сна в условие вместо тела делает цикл более прерываемым (поэтому требование в ответ на аналогичный вопрос о stackoverflow. к сожалению, я могу найти его в данный момент)
- Я хочу выполнить его один раз перед задержкой, поэтому я использую до тех пор, пока не будет