it-swarm-ru.tech

Повторите команду 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 скрипт), но, возможно, есть более стандартный способ сделать это. Если нет, не стесняйтесь размещать решение на вашем любимом языке сценариев.

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."
497
dreeves

Попробуйте команду 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

Или вы можете получить его от Доморощенный :

brew install watch
637
Gregor Brandt

Бака

while + sleep:

while true
do 
    echo "Hi"
    sleep 1
done

Вот то же самое, что сокращенная однострочная (из комментариев ниже):

while sleep 1; do echo "Hi"; done

Использует ; для разделения команд и использования sleep 1 для теста while, поскольку он всегда возвращает true. Вы можете поместить больше команд в цикл - просто разделите их с помощью ;

255
pope

Это просто более короткая версия других while+sleep ответы, если вы выполняете такого рода задачи часто, как свою повседневную жизнь, использование этого избавит вас от ненужных нажатий клавиш, и если ваша командная строка начинает становиться дольше, понять эту задачу будет немного легче. Но этот начинается со сна в первую очередь.

Это обычно полезно, если вам нужно следить за чем-то, имеющим однострочный вывод, такой как загрузка машины:

while sleep 1; do uptime; done
73
Bekir Dogan

Одна из проблем, которые есть во всех ответах, опубликованных до сих пор, заключается в том, что время выполнения команды может измениться. Например, если вы делаете 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?)
}
46
Keith Thompson

Я думаю, что все ответы здесь либо слишком запутаны, либо вместо этого отвечают на другой вопрос:

  • "Как повторно запустить программу, чтобы между ее завершением и следующим запуском была задержка 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 или что там у тебя.

33
swalog

В 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}'
17
Tooony

Недавний bash> = 4.2 под последним ядром Linux, основанный ответ.

Чтобы ограничить время выполнения, нет никаких вилок ! Используются только встроенные.

Для этого я использую встроенную функцию read вместо sleep. К сожалению, это не будет работать с notty сессиями.

Quick bash функция "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 только отдохнуть текущей секунды.

Если задержка важнее и ваша команда не требует значительного времени, вы можете:

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 функция "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
10
F. Hauri

Баш и некоторые из его родственных оболочек имеют удобную (( ... )) обозначение, в котором можно вычислять арифметические выражения.

Итак, в качестве ответа на ваш третий вызов, в котором число повторов и задержка между повторениями должны быть настраиваемыми, вот один из способов сделать это:

repeat=10
delay=1

i=0
while (( i++ < repeat )); do
  echo Repetition $i
  sleep $delay
done

Этот ответ также страдает от временного отклонения, описанного в ответ Кита .

4
Thor

Если вы не хотите выводить сообщение на экран и можете позволить себе повторить работу в минутах, crontab , возможно, будет вашим лучшим инструментом. Например, если вы хотите выполнять свою команду каждую минуту, вы должны написать что-то вроде этого в вашем файле crontab:

* * * * * my_precious_command

Пожалуйста, ознакомьтесь с руководством для дальнейшего примера. Кроме того, вы можете легко установить время, используя Crontab Code Generator .

4
Barun

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); 
}
4
dreeves

Хочешь попробовать это (Баш)?

forever ()   {
    TIMES=shift;
    SLEEP=shift;
    if [ "$TIMES" = "-1" ]; then  
        while true;
        do 
            [email protected]
            sleep $SLEEP
        done
    else
        repeat "$TIMES" [email protected] 
    fi; }
4
Gregg Lind

Как уже упоминал gbrandt, если команда watch доступна, обязательно используйте ее. Однако в некоторых системах Unix она не установлена ​​по умолчанию (по крайней мере, там, где я работаю).

Вот еще одно решение с немного отличающимся синтаксисом и выводом (работает в BASH и SH):

while [ 1 ] ; do
    <cmd>
    sleep <x>
    echo ">>>>>>>>>>>>>" `date` ">>>>>>>>>>>>>>"
done

Правка: я удалил некоторые "." в последнем эхо-заявлении ... пережиток моих дней Perl;)

3
bedwyr

Что если бы у нас были оба?

Вот идея: только с "интервалом", это повторяется навсегда. С "интервалом" и "разом" он повторяет это количество раз, разделенное "интервалом".

Использование :

$ loop [interval [times]] command

Итак, алгоритм будет:

  • Пункт списка
  • если $ 1 содержит только цифры, это интервал (по умолчанию 2)
  • если $ 2 содержит только цифры, это количество раз (по умолчанию бесконечно)
  • цикл while с этими параметрами
    • сон "интервал"
    • если задано несколько раз, уменьшайте переменную, пока она не будет достигнута

Следовательно :

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
}
3
Baronsed

Я закончил, создавая вариант ответа swalog. С его вы должны были ждать X секунд для первой итерации, я также запускаю мой на переднем плане, так что ..

./foo.sh;while sleep 1; do (./foo.sh) ; done
2
Duane Lortie

Вы можете получить мощность от python интерпретатор из Shell.

python3 -c "import time, os
while True:
    os.system('your command')
    time.sleep(5)

замените пять в любое время (сек), которое вам нравится.

Внимание: для возврата используйте SHIFT + ENTER для возврата ввода в Shell. И не забудьте набрать 4 ПРОБЕЛ отступ в каждой строке цикла while.

1
pah8J

Простой способ повторить задание из crontab с интервалом менее одной минуты (пример 20 секунд):

crontab: * * * * * script.sh

script.sh:

#!/bin/bash
>>type your commands here.

sleep 20
>>retype your commands here.

sleep 20
>>retype your commands here.
1
Charles nakhel

Вы можете запустить скрипт из init (добавив строку в/etc/inittab). Этот скрипт должен выполнить вашу команду, подождать, пока вы не захотите снова запустить скрипт, и выйти из него. Init снова запустит ваш скрипт после выхода.

1
Roberto Paz

Быстро, грязно и, вероятно, опасно для загрузки, но если вы любите приключения и знаете, что делаете, поместите это в repeat.sh а также chmod 755 Это,

while true
do 
    eval $1 
    sleep $2 
done

Вызвать его с помощью ./repeat.sh <command> <interval>

Мое чувство пауков говорит, что это, вероятно, злой способ сделать это, верно ли мое чувство пауков?

1
mallyone
#! /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
0
user56318

... Интересно, сколько сложных решений можно создать при решении этой проблемы.

Это может быть так просто ...

open /etc/crontab 

поместите туда 1 следующую строку в конец файла, например:

*/NumberOfSeconds * * * * user /path/to/file.sh

Если вы хотите что-то запускать каждую секунду, просто добавьте туда:

*/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

и это все!

НАСЛАЖДАТЬСЯ!

0
MIrra

С 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:

zmodload zsh/zselect
sleep() zselect -t $((($1 * 100) | 0))
0
Stéphane Chazelas

Вы можете получить эту рекурсивную функцию:

#!/bin/bash
ininterval () {
    delay=$1
    shift
    $*
    sleep $delay
    ininterval $delay $*
}

или добавьте:

ininterval $*

и вызвать сценарий.

0
user unknown

Чтобы повторно запустить команду в окне консоли, я обычно запускаю что-то вроде этого:

while true; do (run command here); done

Это также работает для нескольких команд, например, для отображения постоянно обновляемых часов в окне консоли:

while true; do clear; date; sleep 1; done

0
Thomas Bratt

Это решение работает в 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'

убирать мой рабочий стол постоянно.

0
Dan Ruiz