blob: 8d5ba5a5ba77eb427bd3a69b7c24bd15176907c1 (
plain) (
blame)
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
|
#!/usr/bin/env ruby
# arr EXPR [FILES...] - (re)arrange and select fields on each line
#
# To the extent possible under law,
# Christian Neukirchen <chneukirchen@gmail.com>
# has waived all copyright and related or neighboring rights to this work.
# http://creativecommons.org/publicdomain/zero/1.0/
USAGE = <<'EOF'
Usage: arr [-0] [-P|-p PADDING] EXPR [FILES...]
EXPR ::= FIELDS (("|" CHAR | "*") FIELDS)* # | split on char, * split bytes
FIELDS ::= "~"? FIELD ("," FIELD)* # ~ negates
FIELD ::= "-"? "\d"+ # negative fields count from back
| ("-"? "\d"+)? ":" ("-"? "\d"+)? # range ends default to 1:-1
EOF
require 'strscan'
require 'optparse'
def fmt(ss, d)
last_split = " "
loop {
fields = []
neg = false
begin
i = j = nil
neg = true if ss.scan(/~/)
if ss.scan(/:|-?\d+/)
i = Integer(ss.matched) rescue 1
i = i < 0 ? d.size + i : i - 1
if ss.matched == ":" || ss.scan(/:/)
if ss.scan(/-?\d+/)
j = Integer(ss.matched)
else
j = -1
end
j = j < 0 ? d.size + j : j - 1
if j > i
fields.concat (i..j).to_a
else
fields.concat (j..i).to_a.reverse
end
else
fields << i
end
else
abort "parse error at #{ss.inspect}"
end
end while ss.scan(/,/)
d = d.values_at(*if neg
(0..d.size).to_a - fields
else
fields
end)
if ss.scan(/\|(.)/)
d = d.join(last_split)
last_split = ss[1]
d = d.split(last_split)
elsif ss.scan(/\*/)
d = d.join(last_split)
last_split = ""
d = d.split('')
else
break
end
}
unless ss.scan(/\}/)
abort "parse error at #{ss.inspect}"
end
d.compact.join(last_split)
end
def fmt2(str, arr)
ss = StringScanner.new(str)
r = ""
while ss.scan(/(.*?)%\{/)
r << ss[1]
r << fmt(ss, arr)
end
r << ss.rest
end
begin
params = ARGV.getopts('0Pp:')
nl = params["0"] ? "\0" : "\n"
padding = params["p"] || ""
padding = nil if params["P"]
expr = ARGV.shift or raise OptionParser::MissingArgument, "no EXPR given"
ARGV << "-" if ARGV.empty?
files = ARGV.map { |name|
if name == "-"
STDIN
else
File.open(name, "rb")
end
}
until files.all?(&:eof?)
lines = files.map { |f| f.gets(nl).chomp(nl) rescue padding }
break if lines.include?(nil)
print fmt2(expr, lines), nl
end
rescue OptionParser::ParseError
STDERR.puts $!
STDERR.puts USAGE
end
|