3

I have a Ruby script that needs to run about one time a second. I am using a Ruby script to keep track of modifications of files in a directory and want the script to track updates in "live" time.

Basically, I want my script to do the same kind of thing as running "top" on a Unix shell, where the screen is updated every second or so. Is there an equivalent to setInterval in Ruby like there is in JavaScript?

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Matt Hintzke
  • 7,744
  • 16
  • 55
  • 113
  • Perhaps this helps: http://stackoverflow.com/questions/4747344/watching-a-directory-in-ruby (I didn't check if it works also with file modifications) – knut Dec 09 '12 at 22:01

5 Answers5

15

There are a few ways to do this.

The quick-and-dirty versions:

  1. shell (kornish):

    while :; do
       my_ruby_script.rb
       sleep 1
    done
    
  2. watch(1):

    shell$ watch -n 1 my_ruby_script.rb
    

    This will run your script every second and keep the output of the most recent run displayed in your terminal.

  3. in ruby:

    while true
       do_my_stuff
       sleep 1
    end
    

These all suffer from the same issue: if the actual script/function takes time to run, it makes the loop run less than every second.

Here is a ruby function that will make sure the function is called (almost) exactly every second, as long as the function doesn't take longer than a second:

def secondly_loop
    last = Time.now
    while true
        yield
        now = Time.now
        _next = [last + 1,now].max
        sleep (_next-now)
        last = _next
    end
end

Use it like this:

secondly_loop { my_function }
Michael Slade
  • 13,802
  • 2
  • 39
  • 44
7

You may find interesting this gem whenever

You can code repeating tasks this way:

every 1.second do
  #your task
end
gradenauer
  • 474
  • 3
  • 5
  • 3
    I think it will now work for seconds. whenever setup cron task. you can't setup seconds of time for cron task. – beornborn Jan 15 '14 at 15:09
5

As stated in another answer, rb-inotify is well suited to this sort of thing. If you don't want to use it, then a simple approach is to use threads:

a = Thread.new { loop { some_method; Thread.stop } }
b = Thread.new { loop { sleep 1; break unless a.alive?; a.run } }

To stop polling, use a.kill or make sure that some_method kills its own thread with Thread.kill when some condition is met.

Using two threads like this ensures that some_method runs at least every second, regardless of the length of the operation, without having to do any time checking yourself (within the granularity of the thread scheduling, of course).

Catnapper
  • 1,875
  • 10
  • 12
3

You might want to consider using something like rb-inotify to get notifications of changes of files. This way you can avoid "sleep" and keep the "live" feeling.

There is some useful information at the "Efficient Filesystem Handling" section of the Guard Gem documentation: https://github.com/guard/guard#efficient-filesystem-handling

Wilco
  • 29
  • 2
0

Or you could use TimerTask from the Concurrent Ruby gem

timer_task = Concurrent::TimerTask.new(execution_interval: 1) do |task|
  task.execution_interval.times{ print 'Boom! ' }
  print "\n"
  task.execution_interval += 1
  if task.execution_interval > 5
    puts 'Stopping...'
    task.shutdown
  end
end

timer_task.execute

Code extracted from the same link

16kb
  • 889
  • 9
  • 17