ackぽい再帰的grepを実装してみました

id:ha-tan:20060601:1149174989で触れた再帰grepRubyで実装してみました。ackに比べるとオプションがぜんぜん足りませんが、まぁそれはカスタマイズの範囲ということで。
ファイル: rrr.rb

#!/usr/bin/env ruby

require 'ostruct'

$opts = OpenStruct.new(:debug => 0, 
                       :ignore_case => false)

def match_all_pattern?(line, patterns)
  patterns.each do |pattern|
    return false if line !~ pattern
  end
  true
end

def grep(filename, patterns)
  first_f = true
  open(filename, 'r') do |file|
    while line = file.gets
      break if line.count("\000") > 1 # skip binary file.
      next unless match_all_pattern?(line, patterns)
      if first_f
        print "\n\033[1;32m#{filename}\033[0m\n" # bold green.
        first_f = false
      end
      patterns.each do |pattern|
        line.gsub!(pattern, "\033[30;43m\\&\033[0m") # black on yellow.
      end
      printf "%6d:%s", file.lineno, line
    end
  end
end

if __FILE__ == $0
  require 'find'
  require 'optparse'

  Version = '0.1a'
  OptionParser.new do |opt|
    opt.banner = "Usage: rrr.rb [options] pattern..."
    opt.on('-d', '--debug', 'enable debug mode.') do
      $opts.debug += 1
    end
    opt.on('-i', '--ignore-case', 'ignore case distinctions.') do
      $opts.ignore_case = true
    end
    opt.parse!(ARGV)
    if ARGV.size < 1
      puts opt.help
      exit
    end
  end

  patterns = ARGV.collect do |arg|
    Regexp.new(arg, $opts.ignore_case)
  end
  Find.find('.') do |filename|
    Find.prune if filename =~ %r!/(:?CVS|RCS|.svn|blib)!
    next if FileTest.directory?(filename)
    next if filename =~ /(?:~|\.bak)$/
    puts filename if $opts.debug > 0
    grep(filename, patterns)
  end
end

BUGS。

  • ackの持っているオプションはほとんど実装していません(^^;;
  • 端末以外に出力したときの挙動がackと違います。ファイルであろうとパイプであろうとANSIエスケープシーケンスをそのまま出力します。lv -cとかしてください。
  • ackに比べて動作が遅いです。僕にはこれが限界です。