#!/usr/bin/env ruby # by Rocco Stanzione (2006-11-12) ######################################################################## # Modify a list of lines (in a text file) # # Takes a string, a regular expression, or a file to read, # # and adds or removes lines as instructed. Examples: # # To add lines from one file to another: # # list -a -f sourcefile targetfile # # To add a single line to a file: # # list -a somestring targetfile # # To remove a single line from a file: # # list -r somestring targetfile # # To remove any lines matching a regular expression from a file: # # list -r -x 'someregexp' targetfile # # To remove all lines in fileA from targetfile # # list -r -f fileA targetfile # # To remove all lines from targetfile containing any line from fileA: # # list -r -x -f fileA targetfile # # (note: don't specify a regular expression for this operation, just # # say -x) # ######################################################################## require 'optparse' options = {} OptionParser.new do |opts| opts.banner = "Usage: #{File.basename(__FILE__)} [options] [string] targetfile" opts.on("-a","--add-lines","Add lines to targetfile") { |a| options[:add] = a } opts.on("-r","--remove-lines","Remove lines from targetfile") { |r| options[:remove] = r } opts.on("-f","--file file",String,"Read lines from file") {|f| options[:file] = f } opts.on("-x","--regexp [regexp]",String,"Use regular expression matching") {|x| options[:regexp] = x } @valid_options = opts end.parse! if ARGV.size < 1 puts @valid_options exit 1 end if ARGV.size < 2 and !options.include?(:file) and (!options.include?(:regexp) or options[:regexp].nil?) puts "Must specify a source filename, regular expression, or string" puts @valid_options exit 1 end if options[:regexp] and options[:file] and !options[:regexp].nil? # Can use -x with -f, but not if you specify a regular expression. # So, -x -f foo [target] is valid, but -x [regexp] -f foo [target] is not puts "Cannot specify a regular expression with -f or --file." exit 1 end # the alternative to using -f is using a string, which would be ARGV[0] targetfile = options.include?(:file) ? ARGV[0] : ARGV[-1] sourcefile = options[:file] [targetfile,sourcefile].compact.each do |file| # Make sure any involved files exist, are regular files, and are readable unless FileTest.exist?(file) puts "File #{file} does not exist." exit 1 end unless FileTest.readable?(file) puts "File #{file} is not readable." exit 1 end unless FileTest.file?(file) puts "#{file} is not a regular file." exit 1 end end if options[:regexp] and options[:regexp].size > 0 # Give up on a bogus regexp begin /#{options[:regexp]}/.match("x") rescue puts "Invalid regular expression: /#{options[:regexp]}/" exit 1 end end if options.include?(:add) and not options.include?(:remove) operator = "add" elsif options.include?(:remove) and not options.include?(:add) operator = "remove" end unless ["add","remove"].include?(operator) puts "Must use add or remove as an operator." puts @valid_options end if sourcefile lines=File.readlines(sourcefile) elsif ARGV.size == 2 and !options.include?(:regexp) lines=[ARGV[0]] else puts "Must specify either a string or a file to read from" puts @valid_options exit 1 end newfile = File.readlines(targetfile) if operator == "add" lines.grep!(/#{options[:regexp]}/) if options[:regexp] newfile << lines newfile.flatten! elsif operator == "remove" if sourcefile if options.include?(:regexp) lines.each do |line| newfile = newfile.delete_if{|l|/#{Regexp.escape(line.chomp)}/.match(l)} end else newfile = newfile.delete_if{|l|lines.include?(l)} end else if options[:regexp] and options[:regexp].size > 0 newfile = newfile - newfile.grep(/#{options[:regexp]}/) else newfile = newfile.delete_if{|l|l==lines[0]} end end end write = File.open(targetfile,"w+") write.puts(newfile) write.close puts "operator: #{operator}" puts "lines:" newfile.each{|l| puts " #{l}"} #puts "targetfile => #{targetfile}" #puts "sourcefile => #{sourcefile}"