TECHSCORE Advent Calendar 2016 の 10 日目の記事です。
人情のあるコマンド
例えば、メモリ量を表示する free コマンドには人情があります。
なんと -h とオプションを付加すると、↓のように接頭辞つきの表示をしてくれるのです。
1 2 3 4 |
$ free -h total used free shared buff/cache available Mem: 31G 12G 5.8G 1.0G 12G 17G Swap: 0B 0B 0B |
わかりやすい! 人情!
ファイルに人情はない
しかし、コマンドではなく /proc/$PID/io のようなファイルでは勿論オプションなどありません。
例えば、↓はchromium の IO 統計情報ですが、思わず眉をしかめてしまい人相が悪くなること請け合いです。
何桁あるかわからない!
1 2 3 4 5 6 7 8 |
$ cat /proc/$(pgrep chromium | head -1)/io rchar: 2672946891 wchar: 2802610580 syscr: 35969088 syscw: 40643245 read_bytes: 77824000 write_bytes: 2707628032 cancelled_write_bytes: 20770816 |
ここで専用スクリプトを作るのもアリかと思いますが、毎度そういうことをするのも面倒です。
そこで、「何か数字を見掛けたら人情する」スクリプトを Ruby で書いてみました。
あらかじめ調理しておいたスクリプトがこちらです
人情 などのファイル名にしましょう。
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 |
#!/usr/bin/ruby # vim: set fileencoding=utf-8 filetype=ruby : # # 入力されたテキストの中の数値を 1024 単位で読みやすくする require 'optparse' require 'time' def 人情 (n, unit) return n.to_s if n < unit symbols = 'KMGTPEZY'.split(//) symbol = '' while n > unit symbol = symbols.shift n /= unit.to_f end "%.1f%s" % [n, symbol] end class Quoter def initialize(char) @char = char end def quote (&block) s = block.call if @char and not /\A\d+\z/ === s "#{@char}#{s}#{@char}" else s end end end class String def width self.chars.map{ |c| c.ascii_only? ? 1 : 2 }.inject(0, &:+) end end class Padder def initialize(enabled) @enabled = enabled end def pad (original, &block) s = block.call return s unless @enabled return s if (d = original.width - s.width) <= 0 (' ' * d) + s end end option = {} OptionParser.new do |opt| opt.on('-p REGEXP', '--prefix REGEXP', 'Prefix regexp pattern') {|v| option[:prefix] = v } opt.on('-s REGEXP', '--suffix REGEXP', 'Suffix regexp pattern') {|v| option[:suffix] = v } opt.on('-n REGEXP', '--number REGEXP', 'Number regexp pattern') {|v| option[:number] = v } opt.on('-d', '--[no-]date', 'Read numbers as unixtime (around 10 years)') {|v| option[:date] = v } opt.on('-u 1024/1000', '--unit 1024/1000', '1024 or 1000') {|v| option[:unit] = v.to_i } opt.on('-q', '--[no-]quote', 'Quote values like as String') {|v| option[:quote_char] = v ? "'" : nil } opt.on('-Q', '--[no-]double-quote', '(double) Quote values like as String') {|v| option[:quote_char] = v ? '"' : nil } opt.on('-m', '--min MINIMUM', 'Minimum target number') {|v| option[:minimum] = v.to_f } opt.on('-P', '--[no-]padding', 'Pad with white spaces') {|v| option[:padding] = v } opt.parse!(ARGV) end number = Regexp.new(option[:number] || '\d+(,\d+)*') prefix = Regexp.new(option[:prefix] || '') suffix = Regexp.new(option[:suffix] || '') date = option[:date] quoter = Quoter.new(option[:quote_char]) padder = Padder.new(option[:padding]) min = option[:minimum] whole = /(#{prefix})(#{number})(#{suffix})/ unit = option[:unit] || 1024 now = Time.now.to_i date_d = 60 * 60 * 24 * 30 * 12 * 10 date_range = (now - date_d) .. (now + date_d) date_range_in_millis = date_range.begin * 1000 .. date_range.end * 1000 begin while line = ARGF.gets do line.gsub!(whole) do |it| padder.pad(it) do quoter.quote do p, n, s = $1 || '', $2, $3 || '' n = n.gsub(/[^\d]+/, '').to_i if date and p.empty? and s.empty? if date_range === n next Time.at(n).iso8601 elsif date_range_in_millis === n next Time.at(n / 1000).iso8601 end end next it if min and n < min p + 人情(n, unit) + s end end end puts(line) end rescue Interrupt (0-0) end |
使いかた
コマンドに引数として渡すか、他のコマンドからパイプで流しこむだけです。
1 2 3 4 5 6 7 8 |
$ 人情 /proc/$(pgrep chromium | head -1)/io rchar: 2.5G wchar: 2.6G syscr: 34.3M syscw: 38.8M read_bytes: 74.2M write_bytes: 2.5G cancelled_write_bytes: 19.8M |
1 |
$ tail -f 何かのログ | 人情 |
特定箇所の数値だけを人情したい場合は、-p や -s で Prefix / Suffix となる正規表現を指定します。
例えば、= の直後の数値のみ対象であれば、↓のようにします。
人情してほしくない整数の ID などが含まれる場合などに使う想定です。
1 |
$ 人情 -p '=' |
ARGF 便利
この Ruby スクリプトで使われている ARGF ですが、ちょっと便利なオブジェクトとなっています。
ARGF はファイルオブジェクトのように扱えるのですが、その中身は
ARGV が空であれば標準入力、でなければ内容をファイル名として、それらの中身を連結したもの
と、なっています。
つまり、標準入力と引数指定の両対応スクリプトを簡単に書けるわけです。
ただし、ARGV に余計な要素が残っていると期待しない挙動になります。
ですので↓のように、オプションなどで使用した分は除去しておく必要があります。
1 |
opt.parse!(ARGV) |
ちなみに、ARGF.filename というメソッドがあり、その時読んでいる箇所のファイル名がわかります。
どういう時に便利なんでしょうね。
人情のない生き物
ちなみにこちらが人情のない生き物となります。