-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathrr
executable file
·173 lines (149 loc) · 5.44 KB
/
rr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#!/usr/bin/env ruby
# Start Date: Sunday December 2, 2007 (v0.9)
# Most Recent Update: Saturday January 26, 2008
# Current Version: 1.2
# Author: Joseph Pecoraro
# Contact: [email protected]
# Decription: Default behavior is a multi-line search and replace utility that
# uses a regular expression for searching and allows back references to
# captured groups from the pattern to appear in the replacement text
# Global States
line_processing = false
case_sensitive = false
global_replace = true
modify_original = false
# Usage Message to print
$program_name = $0.split(/\//).last
usage = <<USAGE
usage: #{$program_name} [options] find replace [filenames]
#{$program_name} [options] s/find/replace/ [filenames]
find - a regular expression to be run on the entire file as one string
replace - replacement text, \\1-\\9 and metachars (\\n, etc.) are allowed
filenames - names of the input files to be parsed, if blank uses STDIN
options:
--line or -l process line by line instead of all at once (not default)
--case or -c makes the regular expression case sensitive (not default)
--global or -g process all occurrences in the text (default)
--modify or -m changes will directly modify the original file (not default)
negated options are done by adding 'not' or 'n' in switches like so:
--notline or -nl
special note:
When using bash, if you want backslashes in the replace portion make sure
to use the multiple argument usage with single quotes for the replacement.
example usage:
Replace all a's with e's
#{$program_name} s/a/e/ file
#{$program_name} a e file
Doubles the last character on each line and doubles the newline.
#{$program_name} "(.)\\n" "\\1\\1\\n\\n" file
USAGE
# Print an error message and exit
def err(msg)
puts "#{$program_name}: #{msg}"
exit 1
end
# Possible Switches
ARGV.each_index do |i|
arg = ARGV[i]
if arg[0] == ?- then
case arg
when "--line" , "-l" then line_processing = true
when "--notline" , "-nl" then line_processing = false
when "--case" , "-c" then case_sensitive = true
when "--notcase" , "-nc" then case_sensitive = false
when "--global" , "-g" then global_replace = true
when "--notglobal", "-ng" then global_replace = false
when "--modify" , "-m" then modify_original = true
when "--notmodify", "-nm" then modify_original = false
else err("illegal option #{arg}")
end
end
end
# Remove the Switches from ARGV
ARGV.delete_if { |elem| elem[0] == ?- }
# Check if it is quick mode (meaning cmdline argument is s/find/replace/)
find_str = find = replace = filename = nil
if ARGV.first =~ /^s\/(.*)?\/(.*)?\/(\w*)?$/
find_str = $1
replace = $2
first_filename = 1
ARGV << nil if ARGV.size == 1
# Not Quick Mode, Arguments are seperate on the cmdline
elsif ARGV.size >= 2
find_str = ARGV[0]
replace = ARGV[1].dup
first_filename = 2
ARGV << nil if ARGV.size == 2
# User is allowed to wrap the find regex in /'s (this removes them)
find_str = find_str[1..(find_str.length-2)] if find_str =~ /^\/.*?\/$/
# Bad Arguments, show usage
else
puts usage
exit 1
end
# Make find_str into a Regexp object
if case_sensitive
find = Regexp.new( find_str )
else
find = Regexp.new( find_str, Regexp::IGNORECASE )
end
# Map metacharacters in the replace portion
replace.gsub!(/\\[\\ntrvfbae]/) do |match|
case match
when "\\\\" then match = "\\" # A backslash
when "\\n" then match = "\n" # Newline
when "\\t" then match = "\t" # Tab
when "\\r" then match = "\r" # Carriage Return
when "\\v" then match = "\v" # Vertical Tab
when "\\f" then match = "\f" # Formfeed
when "\\b" then match = "\b" # Backspace
when "\\a" then match = "\a" # Bell
when "\\e" then match = "\e" # Escape
end
end
# Loop through all the filenames doing the find/replace
first_filename.upto(ARGV.size-1) do |i|
# Check for Possible File Errors or if the filename is nil make it STDIN
filename = ARGV[i]
unless filename.nil?
if !File.exist? filename
err("#{filename}: No such file")
elsif File.directory? filename
err("#{filename}: This is a directory, not a file.")
elsif !File.readable? filename
err("#{filename}: File is not readable by this user.")
elsif !File.writable? filename
err("#{filename}: File is not writable by this user.")
end
else
filename = STDIN.fileno
end
# Setup the stream to print to
if modify_original && filename != STDIN.fileno then
temp_filename = filename + '.tmp'
stream = File.new(temp_filename, File::CREAT|File::TRUNC|File::RDWR, 0644)
else
stream = STDOUT
end
# Default Behavior (and basicically what they all do)
# 1. Open the file
# 2. Read text as 1 big sting (memory intensive) or Line by Line
# 3. Run the Find/Replace globally or non-globally
# 4. Print the result to a stream
if line_processing and global_replace then
File.new(filename).readlines.each { |line| stream.puts line.gsub(find,replace) }
elsif line_processing and !global_replace then
File.new(filename).readlines.each { |line| stream.puts line.sub(find,replace) }
elsif !line_processing and global_replace
stream.puts File.new(filename).read.gsub(find,replace)
else
stream.puts File.new(filename).read.sub(find,replace)
end
# If the stream was a temp file then clean up
if modify_original && filename != STDIN.fileno then
stream.close
File.rename(temp_filename, filename)
end
end
# Successful
exit 0