読者です 読者をやめる 読者になる 読者になる

ネットの海の片隅で

技術ネタの放流、あるいは不法投棄。

オフセット付きのheadコマンドを作りました

ShellScript

個人的な話ですが、コードやログを読むときに

$ grep -n 'fizzbuzz' fizzbuzz.rb
543:    puts 'fizzbuzz'

みたいにして、該当箇所を探すということをよくします。

こうして該当行を探した後はその前後を見るためにheadtailを組み合わせて

$ head -n 445 fizzbuzz.rb | tail -n 5

というような操作をするんですが、これが面倒。

head | tailが使いにくい理由

上記のコマンドを実行すると、こんな感じの結果が得られます。

$ head -n 445 fizzbuzz.rb | tail -n 5
arr.each do |n|
  if n % 15 == 0
    puts 'fizzbuzz'
  elsif n % 5 == 0
    puts 'buzz'

途中で切れていて、いかにも気持ち悪いですね。

そこで、5行ではなく10行出力するように書き換えてみます。

$ head -n 450 fizzbuzz.rb | tail -n 10
arr.each do |n|
  if n % 15 == 0
    puts 'fizzbuzz'
  elsif n % 5 == 0
    puts 'buzz'
  elsif n % 3 == 0
    puts 'fizz'
  else
    puts n
  end

10行にしても最後のendが閉じられていませんが、問題はそこではありません。

変更前と変更後それぞれのコマンドに注目してみます。

  • 変更前:head -n 445 fizzbuzz.rb | tail -n 5
  • 変更後:head -n 450 fizzbuzz.rb | tail -n 10

5行出力するのを10行に変更したので、tailの引数が5から10に変わっているのは良いのですが、なぜかheadの引数まで変わっています。

出力行数を変えるだけなのに2箇所に変更を加えないといけない上、片方は一見関係なさそうなところにある。 さらに、head -n 445 fizzbuzz.rb | tail -n 5が出力する先頭行は直感的には445-5=440なので440行目かと思いきや、441行目が先頭に来ます。

head | tailの面倒なところまとめ

  • 表示行数を変えるために2箇所に変更を加えないといけない
  • しかも、変更箇所の片方はあまり関係なさそうなところにある
  • n行目から表示するためには引数にn-1を指定しなければならず非直感的

tail | headが使いにくい理由

head | tailと同じようなことはtail | headでも出来ますが、これも面倒です。

$ wc -l fizzbuzz.rb
987
$ tail -n 547 fizzbuzz.rb | head -n 5
$ tail -n 547 fizzbuzz.rb | head -n 10

head | tailとは違って、変更箇所は1箇所で済んでいます。

ただ、head | tailのときには使っていなかったwcが必要になりますし、引数の547ってどこから来たんだよっていう感じです。

さらに、コードのようなあまり変化しないファイルであれば良いのですが、ログファイルなどは行数が随時変化するため、その度にwcの結果を用いてtailの引数を計算し直さなければなりません。1秒間に数百回コマンドを書き換えるだけです。簡単ですね☆

tail | headの面倒なところまとめ

  • wcを使う必要がある上、1行で完結しない
  • 987-440 のような超複雑な暗算をしなければならない
  • 行数が変化するファイルには事実上使用できない

やりたいこと

やりたいことをまとめると、こんな感じです。

  • あるファイルのn行目からm行だけ出力したい

シンプルですね。

やりたいことは「ファイルの真ん中を出力する」だけです。

これ以上ないくらいシンプルですが、簡単には実現できませんでした*1

ribs

というわけで、簡単なシェルスクリプトを書きました。

s-osa/ribs · GitHub

ファイルの先頭を取るのがhead(頭)、末尾を取るのがtail(尻尾)。 だったら、間を取るのはribs(肋骨)とかで良いのでは? というネーミングです。

リンク先のREADMEにも書いてありますが、こんな感じで使います。

$ ribs -o 441 -n 5 fizzbuzz.rb
arr.each do |n|
  if n % 15 == 0
    puts 'fizzbuzz'
  elsif n % 5 == 0
    puts 'buzz'

構文

ribs [-o offset] [-n number_of_rows] file

オプション引数

オプション 引数
o 出力する先頭行の行番号。デフォルト値は1。
n 出力する行数。デフォルト値は10。

さいごに

実はシェルスクリプトを真面目に書いたのは初めてです。 今まではcronに登録するやつとか、他の言語で書かれたスクリプトを叩くだけのものとか、そんな簡単なものしか書いてきませんでした。

オプション引数を設定できると自由度も上がると思うので、今後は簡単なシェルスクリプトをちょいちょい書いていきたいと思います。

あと、今回のribsを作成するにあたり、head, tail, catあたりのオプションはひと通り調べましたが、「n行目から」というのを実現するオプションは見当たりませんでした。もし、存在するなら是非教えて下さい。

[改訂新版] シェルスクリプト基本リファレンス  ??#!/bin/shで、ここまでできる (WEB+DB PRESS plus) UNIXという考え方―その設計思想と哲学

*1:少なくとも僕のUNIX力では。