I'm working on a search utility that searches through source files. It's only the beginning of the project. It lacks a lot of features, but a backbone of the project works and it would be great if you can take a look at it.

path, keyword = ARGV if ARGV.length == 2

if ARGV.length != 2
  puts "Not enough argumetns. Type 'finder --help' for help" 
  abort if ARGV.length != 2

IGNORED_DIRS = ['..', '.', '.git', 'blib', '_build', '.bzr', '.cdv', 'cover_db',
                '__pycache', 'CVS', '_darcs', '~.dep', '~.dot', '.hg', '~.nib', '.pc', '~.plst', 'RCS', 'SCCS',
               '_sgbak', '.svn', '.tox','.metadata', '.cover']

# colorize output
class String
  def black;          "\033[30m#{self}\033[0m" end
  def red;            "\033[31m#{self}\033[0m" end
  def green;          "\033[32m#{self}\033[0m" end
  def brown;          "\033[33m#{self}\033[0m" end
  def blue;           "\033[34m#{self}\033[0m" end
  def magenta;        "\033[35m#{self}\033[0m" end
  def cyan;           "\033[36m#{self}\033[0m" end
  def gray;           "\033[37m#{self}\033[0m" end
  def bg_black;       "\033[40m#{self}\033[0m" end
  def bg_red;         "\033[41m#{self}\033[0m" end
  def bg_green;       "\033[42m#{self}\033[0m" end
  def bg_brown;       "\033[43m#{self}\033[0m" end
  def bg_blue;        "\033[44m#{self}\033[0m" end
  def bg_magenta;     "\033[45m#{self}\033[0m" end
  def bg_cyan;        "\033[46m#{self}\033[0m" end
  def bg_gray;        "\033[47m#{self}\033[0m" end
  def bold;           "\033[1m#{self}\033[22m" end
  def reverse_color;  "\033[7m#{self}\033[27m" end

# convert given path to full path
# which can be used in 'Dir.chdir()'
def expand_path(path)
  case path
  when '.'
  when /~(\/[a-zA-Z\w]*)+/
    File.expand_path(path, __FILE__)    
  when /\.\/[a-z]*/
    Dir.pwd + path[1..-1]
  when /\/[a-z]*/
    Dir.pwd + path
    puts "Wrong path name"

def search_in_file(path_to_file, keyword)
  f = open(path_to_file)

  flag = true

  f.each_with_index do |line, i|
    if line.include? keyword

      # print file name and path
      if flag
        puts ""
        puts path_to_file.bold.blue
        flag = false
      puts "#{i+1}:".bold.gray + " #{line}".sub(keyword, keyword.bg_red) 

def file_finder(path, keyword)

  entries = Dir.entries('.')

  entries.each do |item|
    unless IGNORED_DIRS.include?(item)
      if File.file?(path + "/" + item)
        search_in_file(path + "/" + item, keyword)
        file_finder(path + "/" + item, keyword)

file_finder(expand_path(path), keyword)  
  • \$\begingroup\$ Anything in particular you are concerned about? \$\endgroup\$ Apr 27, 2015 at 23:06
  • \$\begingroup\$ I am newcomer to Ruby and my knowledge about language and how should I approach problem using Ruby is very limited right now. So, I just like to hear some feedback, may be some weak points and some improvements I can take. May be some Ruby way of how to do such and such \$\endgroup\$ Apr 28, 2015 at 8:30

2 Answers 2


The POSIX grep command accepts the keyword as the first parameter, then the paths to search as subsequent parameters. That has the advantage of letting the user specify any number of paths to search.

You misspelled "argumetns". Also, I recommend writing "#{File::basename($0)}" rather than hardcoding "finder" as the program name.

It is customary to put all of the class / method definitions first, then all of the "main" code at the end. You've put the argument-handling code at the top, and the file_finder(expand_path(path), keyword) call at the bottom. Curiously, you test ARGV.length in three places — is it really that important?


Avoid using Dir#chdir, as changing the current directory alters the global state of the process. One consequence of Dir#chdir is that you need to use absolute paths. For portability, use File::join(path, item) instead of path + "/" + item.

For efficiency, IGNORED_DIRS should be a Set.

def file_finder(path, keyword)
  Dir.foreach(path) do |item|
    next if IGNORED_DIRS.include?(item)

    item_path = File::join(path, item)
    if File.file?(item_path)
      search_in_file(item_path, keyword)
      file_finder(item_path, keyword)


Your expand_path incorrectly treats an absolute path as a relative path.

When expanding a path starting with ~, I don't see any reason to provide __FILE__ as the second argument to File.expand_path.

In case of an error, you should raise an exception instead of printing a message here. However, I don't see any reason why there should be an error in the first place.

If you avoid changing directories altogether, then you wouldn't need to generate absolute paths at all. Then you could just eliminate this function and call File::expand_path instead.


Opening a file without closing it causes a file descriptor leak.

flag is a poor variable name.

I'm not a fan of augmenting the String class, especially for code that is, strictly speaking, related more to terminal control than strings.

def search_in_file(path_to_file, keyword)
  seen = false
  File::open(path_to_file) do |f|
    f.each_with_index do |line, i|
      if line.include?(keyword)
        if not seen
          puts path_to_file.bold.blue
          seen = true
        puts "#{i+1}:".bold.gray + " #{line}".sub(keyword, keyword.bg_red)
  puts "" if seen
  • \$\begingroup\$ Thank you for File::join(path, item) I didn't know about that. I guess if I decided to make my app works on Windows it would be handy. \$\endgroup\$ Apr 28, 2015 at 8:48
  • \$\begingroup\$ in file_finder you said that I should avoid Dir#chdir. Could you suggest another ways of doing this? \$\endgroup\$ Apr 28, 2015 at 9:00
  • \$\begingroup\$ I already did. This solution works. \$\endgroup\$ Apr 28, 2015 at 9:07
  • \$\begingroup\$ I like your file_finder implementation. It's much robust isn't it? \$\endgroup\$ Apr 28, 2015 at 9:09
  • \$\begingroup\$ does File::open prevent File descriptor leaks? If so, how? I don't see any file.close at the end \$\endgroup\$ Apr 28, 2015 at 9:14
  • Whenever we make utility type of ruby scripts please make use of option parser to deal with options parameter.
  • Encompose the methods inside a class. So, that the methods can be written without parameter.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.