it-swarm.cn

永远每隔X秒重复Unix命令

有一个内置的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

将会每秒运行一次命令(从技术上讲,每秒运行一次,再加上commandwatch(至少procpsbusybox实现)只是在两次command)之间永远睡一秒钟。

您是否要将命令传递给exec而不是sh -c, 采用 -x 选项:

watch -n1 -x command

在macOS上,您可以从 Mac Ports 中获得watch

port install watch

或者您可以从 Homebrew 中获得它:

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不同)。该时间间隔是相对于纪元计算的,因此将在UTC午夜运行86400秒(1天)。

#!/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.shwhile 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"可以替换为带反引号(`)的“ any”命令。

为了暂停,在for循环内添加sleep语句:

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

在最近的Linux内核下,基于最近的bash> = 4.2。

为了限制执行时间,没有分叉!仅使用内置的。

为此,我使用read内置函数代替sleep。不幸的是,这不适用于notty会话。

快速 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包含以纳秒为单位的时间信息。

如果您想精确地每秒运行一次命令,那么您的命令必须在不到一秒钟的时间内结束! And从那里,您只需要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 函数“ 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

Bash及其某些同类外壳具有方便的(( ... ))符号,其中可以评估算术表达式。

因此,作为您的第三个挑战的答案,即重复次数和每次重复之间的延迟都应是可配置的,这是一种解决方法:

repeat=10
delay=1

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

此答案还遭受 Keith's答案 中涵盖的时序漂移。

4
Thor

如果您不打算在屏幕上显示消息,并且如果您愿意负担几分钟的时间重复工作,则 crontab 也许是最好的工具。例如,如果您希望每分钟执行一次命令,则可以在crontab文件中编写如下代码:

* * * * * my_precious_command

请查看该教程以获取更多示例。另外,您可以使用 Crontab代码生成器 轻松设置时间。

4
Barun

佩尔

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

编辑:我删除了一些“。”在最后一个echo语句中...我的Perl日子中的保持状态;)

3
bedwyr

如果我们两个都怎么办?

这是一个主意:只有“间隔”,它会永远重复。对于“间隔”和“时间”,它会重复此次数,并以“间隔”分隔。

用法:

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

我最终决定在swalog的答案上创建一个变体。有了他,您必须等待X秒才能进行第一次迭代,因此我也在前台运行。

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

您可以从Shell的python解释器)获取功能。

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

在您喜欢的任何时间(秒)替换五个。

注意:返回使用SHIFT + ENTER在Shell中返回输入。并且不要忘记在while循环的每一行中键入4 [〜#〜] space [〜#〜]缩进。

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.shchmod 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

如果您想每1秒运行一次,只需将其放到那里:

*/60 * * * * root /path/to/file.sh 

where that file.sh could be chmod 750 /path/to/file.sh

在该file.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不支持浮点数,则始终可以将其重新定义为zshzselect内置函数的包装:

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