Written
on
On file change run...
Problem
I often find myself wanting to re-run some command (say
pdflatex
) when some file changes.
It’s not a particularly difficult problem, but here’s my take on it.
Solution
#!/usr/bin/ruby
unless ARGV.size == 2
puts "Usage: #{File.basename($0)} <file> <command>"
exit 1
end
file = ARGV.shift
command = ARGV.shift
unless test(?f, file)
puts "Invalid file: #{file}"
exit 1
end
mt = File.stat(file).mtime
pid = nil
puts "+++ Watching ..."
begin
loop do
Process.wait(-1, Process::WNOHANG) rescue nil
mt_new = File.stat(file).mtime rescue next
unless mt_new == mt
puts "+++ File (#{file}) changed ... launching command."
if pid
Process.kill(9, pid) if (Process.kill(0, pid) rescue nil)
pid = nil
end
pid = fork do
Kernel.exec("sh", "-c", command)
end
puts "+++ Watching ..."
mt = mt_new
end
sleep 0.5
end
rescue Interrupt
end
Updated solution from ca. 2014
#!/usr/bin/ruby
if ARGV.size < 2
puts "Usage: #{File.basename($0)} <file/glob> <command> <params>*"
exit 1
end
file = ARGV.shift
command = ARGV
refresh = proc { Dir[file] }
files = refresh.call
if files.all? { |x| FileTest.file?(x) }
puts "Watching: '#{file}' (#{files.size} entries as of now)"
else
puts "Invalid file/glob: #{file}"
exit 1
end
mt = nil # mtimes
pid = nil
puts "+++ Watching ..."
begin
loop do
Process.wait(-1, Process::WNOHANG) rescue nil
files = refresh.call unless refresh.nil?
mt_new = files.map { |x| File.stat(x).mtime rescue nil }
if mt && mt_new != mt
puts "+++ File (#{file}) changed ... launching command."
if pid
Process.kill(9, pid) if (Process.kill(0, pid) rescue nil)
pid = nil
end
pid = fork do
Kernel.exec("sh", "-c", *command)
end
puts "+++ Watching ..."
end
mt = mt_new
sleep 0.5
end
rescue Interrupt
end