あるコマンド foo で、こんな出力がある
$ ./foo
"EB80"
"EB81"
"EB82"
"EB83"
"EB84"
"EB85"
"EB87"
"EB88"
"EB89"
"EB8A"
"EB8B"
"EB8D"
Unicodeの羅列なわけだけど、これが連続しているかどうかを確認したい。
普通に、Unixのコマンドで頑張ってみると
$ ./foo | tr -d '"' | (echo ibase=16; cat) | bc | awk 'last+1 == $0{last=$0; print "..."; next} {print last; last=$0; print $0} END {print last}' | uniq
60288
...
60293
60295
...
60299
60301
なんとかここまで来たが、この10進数を16進数に変換する処理がいまいちである。bcでやってみると、"..."の出力のせいでエラーになる(まあ、目的は達成している)
$ ./foo | tr -d '"' | (echo ibase=16; cat) | bc | awk 'last+1 == $0{last=$0; print "..."; next} {print last; last=$0; print $0} END {print last}
' | uniq | (echo ibase=10; echo obase=16; cat) | bc
EB80
(standard_in) 5: syntax error
EB85
EB87
(standard_in) 8: syntax error
EB8B
EB8D
ruby で頑張ってみることにする。
まずは、連続値をうまく扱うクラスがあった方がいろいろとうれしいんじゃないかと思った。
(結構、大きくなったので保存のためにこの記事を書くことにした)
discrete_range.rb
#!/usr/bin/env ruby
class DiscreteRange
include Enumerable
def initialize(*arg)
@ranges = []
arg.each {|v| add v}
end
def add(v)
entry = @ranges[-1]
case entry
when Range
if !entry.exclude_end? && entry.last.succ == v
@ranges[-1] = Range.new(entry.first, v)
elsif entry.exclude_end? && entry.last == v
@ranges[-1] = Range.new(entry.first, v)
else
@ranges.push v
end
else
if entry.respond_to?(:succ) && entry.succ == v
@ranges[-1] = Range.new(entry, v)
else
@ranges.push v
end
end
end
def each
@ranges.each {|e|
case e
when Range
e.each {|v|
yield v
}
else
yield e
end
}
end
def to_s
@ranges.to_s
end
end
if $0 == __FILE__
dr = DiscreteRange.new(1,2,3,5)
p dr # => #<DiscreteRange:0x0000060042f5f8 @ranges=[1..3, 5]>
p dr.to_a # => [1, 2, 3, 5]
dr.add 6
p dr # => #<DiscreteRange:0x0000060042f238 @ranges=[1..3, 5..6]>
end
./foo | tr -d '"' | ruby -r./discrete_range.rb -e 'args = ARGF.readlines.map(&:chomp)' -e 'dr = DiscreteRange.new(*args)' -e 'puts dr'
["EB80".."EB85", "EB87".."EB89", "EB8A".."EB8B", "EB8D"]
たまたまいいサンプルを選んだが、EB89, EB8A は16進数で連続しているのにそう見なされていない。やっぱり10進数⇔16進数を相互変換するコマンドは欲しい。
とりあえずrubyで代用する
$ ./foo | tr -d '"' | ruby -ne 'puts $_.hex' | ruby -r./discrete_range.rb -e 'args = ARGF.readlines.map(&:chomp)' -e 'dr = DiscreteRange.new(*args)' -e 'puts dr' | ruby -pe '$_.gsub!(/\d+/) { $&.to_i.to_s(16).upcase }'
["EB80".."EB85", "EB87".."EB8B", "EB8D"]
もちろんパイプを使わずに全部一つのrubyでやってもいいが、Unix的哲学に反する気がするので、役割分担の意味でコマンドを分けている。
一応、rubyでやってみたがやはりワンライナーでは適材適所があると思う。
$ ./foo | ruby -r./discrete_range.rb -e 'args = ARGF.readlines.map {|v| v.delete("\"")}.map(&:hex)' -e 'dr = DiscreteRange.new(*args)' -e 'puts dr.to_s.gsub(/\d+/) { $&.to_i.to_s(16).upcase }'
[EB80..EB85, EB87..EB8B, EB8D]