-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathline
executable file
·198 lines (172 loc) · 4.32 KB
/
line
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
#!/usr/bin/env ruby
# Start Date: Sunday January 4, 2009
# Current Version: 1
# Author: Joseph Pecoraro
# Contact: [email protected]
# Decription: Grab certain lines of a file
# with a simple syntax (see usage). Silent
# usage useful for piping output and Pipe
# option for piping input into it
# Wrapper around functionality
class FileLines
attr_accessor :lines, :stdin
def initialize(options, filename=nil)
if filename.nil?
@stdin = true
@lines = []
while line = STDIN.gets
@lines << line
end
else
@stdin = false
@lines = File.readlines(filename)
end
@silent = false
parse_options(options)
end
def parse_options(options)
options.each do |option|
if option.match(/^-(-silent|s)$/)
@silent = true
end
end
end
def parse(args)
args.each do |arg|
# String: all
if m = arg.match( /^all$/ )
print_lines(0, @lines.size)
# Ranges: 3-7
elsif m = arg.match( /^(\d+)-(\d+)$/ )
lower = m[1].to_i
upper = m[2].to_i
lower = 1 if lower.zero? # 0 => 1
print_lines( lower-1, upper-lower+1 ) if upper >= lower
# Lookarounds: 3~7 <before_and_after>~<line>
elsif m = arg.match( /^(\d+)?~(\d+)$/ )
range = m[1].nil? ? 2 : m[1].to_i
lineno = m[2].to_i
lower = lineno-range
lower = 1 if lower < 0
upper = lineno + range
print_lines( lower-1, upper-lower+1 )
# Down: *7
elsif m = arg.match( /^\*(\d+)$/ )
lower = 1
upper = m[1].to_i
print_lines( lower-1, upper-lower+1 )
# Up: 7*
elsif m = arg.match( /^(\d+)\*$/ )
lower = m[1].to_i
upper = @lines.size
print_lines( lower-1, upper-lower+1 )
# Skipping 2:1/2 <offset>:<range>/<skip>
elsif m = arg.match( /^((\d+):)?(\d+)\/(\d+)$/ )
offset = m[2].nil? ? 1 : m[2].to_i
range = m[3].to_i
skip = m[4].to_i
print_with_skips(offset, range, skip)
# Single Lines
else
lineno = arg.to_i
lineno = 1 if lineno.zero? # 0 => 1
lineno = lineno-1 if lineno > 0 # Handle Negatives
print_line( lineno )
end
end
end
def print_lines(lower, upper)
return if lower > @lines.size
i = lower
@lines[lower, upper].each do |line|
if @silent
puts line
else
puts "[#{i+1}]: #{line}"
end
i += 1
end
end
def print_line(line)
real = line>@lines.size ? @lines.size-1 : line
if @silent
puts @lines[real]
else
if line < 0
puts "[#{real}]: #{@lines[real]}"
else
puts "[#{real+1}]: #{@lines[real]}"
end
end
end
def print_with_skips(offset, range, skip)
offset = 1 if offset < 0
range = 1 if range < 1
skip = 1 if skip < 1
cnt = 0
lineno = offset-1
skipping = false
@lines[offset-1, @lines.size].each do |line|
lineno += 1
cnt += 1
if skipping
if cnt >= skip
skipping = false
cnt = 0
end
next
else
if cnt >= range
skipping = true
cnt = 1
end
if @silent
puts line
else
puts "[#{lineno}]: #{line}"
end
end
end
end
end
# When run as as script
if $0 == __FILE__
# Pull out the Options
options = ARGV.select { |arg| arg.match(/-[a-z]/i) }
ARGV.delete_if { |arg| arg.match(/-[a-z]/i) }
# Special Handling for Pipes (don't require a filename)
is_pipe = !STDIN.tty?
# Check Cmd Line Args and Print Usage if needed
if ARGV.size <= 1 && !is_pipe
puts "usage: line [options] filename numbers"
puts
puts " options:"
puts " --silent or -s Just print the line, without [#]"
puts
puts " number formats:"
puts " 1-3 Prints lines 1 through 3"
puts " 5 Prints line 5"
puts " -1 Prints the last line, (negative)"
puts
puts " extra formats:"
puts " all Prints out all lines"
puts " ~5 Prints 2 (default) lines before and after 5"
puts " 4~10 Prints 4 lines before and after 10"
puts " *7 or 8* Prints all lines before or after the number"
puts " 5/1 Prints 5 lines, then skips 1 line..."
puts " 2:5/1 Starts at line 2, prints and skips..."
puts
exit 1
end
# Pipe Usage
if is_pipe
FileLines.new(options).parse(ARGV)
# Unreadable File
elsif !File.readable?(ARGV[0])
puts "line: file '#{ARGV[0]}' did not exist or was unreadable."
exit 1
# Run the Program
else
FileLines.new(options, ARGV[0]).parse(ARGV[1, ARGV.size])
end
end