Unix を使って感激することの一つはパイプラインでしょう。例えば、ruby という文字列を含むファイルを検索するとき locate を使いますが、ファイルの数が多いと大部分が画面から消えてしまいます。そのようなときに、locate ruby | less のように locate の出力をページャにパイプさせるのはよくやる操作です。また、locate の出力をさらに sample という文字列で絞りこみたい時があります。そのようなときには、locate ruby | grep sample のように locate の出力を grep にパイプしてやります。Unix の特徴はこのようにツールを組合せてデータを操作することが簡単にできることです。
しかし、locate の出力は単なる文字列なので、目的のファイルを開こうと思ったら、less の後に長いパス名をキーボードから入力しなくてはなりません。もし、locate からの出力が HTML ファイルになっていてそれをブラウザーで表示できたらかなり便利でしょう。実は、それは可能なのです。また、locate の出力とブラウザの間に grep で絞りこみのフィルターをかけることもできるのです。
このページでも紹介した w3m はキャラクターベースの WWW ブラウザーですが、標準入力から HTML 文書を読み込んでレンダリングすることができます。それで、結果の表示にはこの w3m を利用することにします。
出力の表示は簡単にできそうなので、locate の出力を HTML にするのに Ruby を使いましょう。次のスクリプトを locate_h.rb で作成します。
#!/usr/local/bin/ruby p = IO.popen( "locate #{ARGV[0]}", "r" ) p.each_line { |line| line.chop!; print "<a href=\"#{line}\">#{line}</a><br>\n" } p.close
一行目のパスは自分の環境に合わせて変更してください。また chmod +x locate_h.rb でファイルを実行可能にしてください。それでは、コンソールから次のようにタイプしてテストして見ましょう。
$ ./locate_h.rb ruby
HTML 形式のファイルリストが画面に表示されれば成功です。コマンドの先頭に ./ をつけたのは最近のディストリビューションの設定ではカレントワーキングディレクトリーはコマンドサーチパスに登録されていないからです。コマンドサーチパスに登録されている(かもしれない) ~/bin/ ディレクトリーに locate_h.rb ファイルをいれておけば ./ は省略できます。それではいよいよ本番です。locate_h.rb の出力を w3m へパイプします。次のように入力してください。
$ ./locate_h.rb ruby | w3m -T text/html
ハイーパーリンクされた、ファイルリストが表示されたはずです。リンクにカーソルを移動して CR を押すだけでファイルを見ることができます。Shift + B で元のリストに戻ります。ちょっと感動的です。さらに ruby 関連のファイルのうち特に sample ディレクトリーのファイルを見たいときは grep でフィルタリングすることができます。
$ ./locate_h.rb ruby | grep sample | w3m -T text/html
w3m -T text/html は少し長いので alias w3m_h="w3m -T text/html" でエイリアスを作ります。また locate_h.rb もコマンドサーチパスの通った ~/bin/ ディレクトリーに移しておきます。すると上の操作は次のようになります。
$ locate_h.rb ruby | grep sample | w3m_h
通常のストリームを使っているのと全く同じ感覚で使えます。w3m_h は標準入力からの HTML 文書を読み込めますから、namazu の出力を -h オプションで HTML 文書にしたときの受け皿にすると便利です。具体的には次のようにします。namazu の利用法についてはこのホームページの他の場所で詳しく述べています。
$ namazu -h ruby ~/index/ | w3m_h
locate_h.rb は便利ですが、grep のフィルターを使わない場合もっとキー入力を少なくしたい気もします。そんなときは、次の wlocate.rb を使うこともできます。
#!/usr/local/bin/ruby pr = IO.popen( "locate #{ARGV[0]}", "r" ) pw = IO.popen( "w3m -T text/html", "w" ) pr.each_line { |line| line.chop!; pw.print "<a href=\"#{line}\">#{line}</a><br>\n" } pr.close pw.close
これは次のように使います。
$ wlocate.rb ruby
キー入力が随分減って楽になりました。locate_h.rb も wlocate.rb も IOクラスの popen メソッドを使っています。popen メソッドは子プロセスを実行してその標準入力および標準出力を Ruby の IO オブジェクトに繋げるメソッドです。popen の使い方は、ファイルを開くときと同じ感覚です。popen の第1引数は、実行する子プロセスのコマンドライン入力で、第2引数は Ruby 本体のスクリプトが子プロセスからの標準出力を読み取るときは "r" で、子プロセスの標準入力に書き込むときは "w" です。wlocate を例にとると、コマンドラインから次のように入力したのと同じになります。
$ locate ruby | Ruby 本体の処理 | w3m -T text/html
スクリプトの最後に、pr.close と pw.close でIOオブジェクトを閉じます。IO オブジェクトは close しないとハングアップしてしまうので注意が必要です。
grep も HTML 化したいコマンドの一つです。次のスクリプトを wgreb.rb で作成します。
#!/usr/local/bin/ruby ARGV[1] = '*' if ARGV[1] == nil pwd = `pwd`.chop! # pr = IO.popen("grep #{ARGV.join(' ')}", "r") pr = IO.popen("grep -H #{ARGV.join(' ')}", "r") pw = IO.popen("w3m -T text/html", "w") pr.each_line do |line| line =~ /.*?:/ f = $&.dup.chop! s = $'.dup.chop! f = pwd + '/' + f if f =~ /[^\/]/ s2 = s.gsub(/&/n, '&').gsub(/\"/n, '"').gsub(/>/n, '>').gsub(/</n, '<') pw.print "<a href=\"#{f}\">#{f}</a><br>" pw.print "#{s2}<br><br>\n" end pr.close pw.close
1行目のパスは環境に合わせて設定する必要があります。grep のバージョンが古いと -H オプション(ファイル名を必ず表示する)がない場合があります。その場合は5行目の文を # でコメントアウトして、4行目の # を消して使ってください。wgrep へのコマンドライン引数は直接 grep へ渡されるので、wgrep.rb の使い方は、ほとんど grep と同じになります。使い方は次のようになります。
$ ./wgrep.rb print *
grep に比べて速度は遅くなりますが、ハイパーリンクでファイルを閲覧できます。また、grep よりちょっぴり賢くなっていて次のように第2引数を省略するとカレントワーキングディレクトリーの全てのファイルからキーワードを検索してくれます。
$ ./wgrep.rb print
wgrep.rb で使った文で説明をしなければならないのは2行目の `pwd` くらいです。バッククォートでシェルコマンドを囲むとその出力が文字列または配列となって返されます。バッククォートの中では変数の展開もすることができます。
wgrep を使っていてもファイルが多く grep でフィルタリングしたいと思うこともあります。wgrep を改造して、出力を標準出力に出すようにしましょう。それには、wgrep のスクリプトのうち w3m へのパイプに関する部分を削除するだけです。次のスクリプトを hgrep.rb というファイルに作成します。機能削減による改良(?)です。
#!/usr/local/bin/ruby ARGV[1] = '*' if ARGV[1] == nil pwd = `pwd`.chop! # pr = IO.popen("grep #{ARGV.join(' ')}", "r") pr = IO.popen("grep -H #{ARGV.join(' ')}", "r") pr.each_line do |line| line =~ /.*?:/ f = $&.dup.chop! s = $'.dup.chop! f = pwd + '/' + f if f =~ /[^\/]/ s2 = s.gsub(/&/n, '&').gsub(/\"/n, '"').gsub(/>/n, '>').gsub(/</n, '<') print "<a href=\"#{f}\">#{f}</a><br>" print "#{s2}<br><br>\n" end pr.close
これで、grep によるフィルタリングが可能になりました。次のように使います。
$ hgrep.rb print | grep name | w3m_h
ここまで述べたツールは全て、実はシェルスクリプトと Perl で作ってこのホームページの他の所で紹介したものです。しかし、Ruby を使うと非常にすっきりと記述することができます。
これは HTML ファイルからアンカーだけを取り出すプログラムです。次のように使うと index.html からアンカーだけを取り出して w3m でリンクをたどることができます。
$ anchor.rb index.html | w3m_h
カレントディレクトリーの全ての HTML ファイルからリンクを抜き出す事ができます。
$ anchor.rb *.html | w3m_h
結果をファイルに保存することもできます。
$ anchor.rb index.html > anchors.html
index.html ファイルの中のアンカー(リンク)の数を数えることもできます。
$ anchor.rb index.html | wc -l 32
links.html ファイルのアンカーから w3m を含むものだけを取り出します。
$ anchor.rb links.html | grep w3m | w3m_h
anchor.rb のスクリプトは下のようになります chomod +x anchor.rb として実行可能にして、コマンドサーチパスの通ったディレクトリーに入れておくと便利です。
#!/usr/local/bin/ruby line = '' while ( s = gets ) line += s.chop! if line !~ /<a href/ line = '' next else while line =~ /<a href.*?<\/a>/ print "#{$&}<br>\n" line = $'.dup end end end
anchor.rb の実用的な用途を無理に考えると、リンク集の要約ができるかなと言う気もしますが、実用性ゼロと考えるのが素直です。でも、面白い。
今度はテキストファイルを HTML ファイルに変換してみましょう。変換に際しての簡単なルールを決めておきます。つまり、テキストファイルの1行が HTML ファイルの<p>と</p>に挟まれた段落に変換されます。また <pre> だけの行と </pre> だけの行に囲まれた部分はそのまま変換せずに置きます。たった二つだけの規則ですが個人的な HTML 文書を作るのには十分です。
text2html.rb の使い方は次のようになります。
$ ./text2html.rb test.txt
で画面に表示して変換がきちんと行われるか確認します。
$ ./txt2html.rb test.txt | w3m_h
でレンダリングがうまくいくか確認できます。
$ ./txt2html.rb test.txt > test.html
で html 文書をファイルに保存します。
txt2html.rb のスクリプトは次のようになります。
#!/usr/local/bin/ruby class String def convert! self.gsub!(/\&/, '&') self.gsub!(/\"/, '"') self.gsub!(/\</, '<') self.gsub!(/\>/, '>') self end end line = gets while ( line ) line.chop! if line == '<pre>' puts line ln = gets.chop! while ( ln != '</pre>' ) puts ln.convert! ln = gets.chop! end puts ln else print "<p>#{line.convert!}<\/p>\n" end line = gets end
ここで強調したいテクニックは冒頭の class String の部分です。何と、組み込みクラスである String クラスに新しいメソッド convert! を定義できてしまうのです。そうしてこの convert! は String クラスのどのオブジェクトにも他の組み込みメソッドと同じように使うことができるのです。(うれしい!)
ところで convert! メソッドは名前の最後に ! がつけられていますがその場合は object.convert! でメソッドを呼び出した場合 object の内容が変わってしまうことを示します。a.chop は文字列 a の最後の一文字を取り去ったオブジェクトを返しますが a 自身はそのままです。a.chop! の場合は a 自身の最後の文字を取り去ります。String クラスには include? と最後に ? がついたメソッドがありますがこれはそのオブジェクトの中に引数の文字列があるかどうかを問い合わせるメソッドです。! や ? 自身に特別な意味があるわけではありませんが、これをメソッドの名前につけることによってメソッドの操作の種類を暗示しているわけです。
次のような test.txt を作成して、
これは txt2html.rb のテストです。&, ", <, >, は自動的に変換されます。 <pre> for i in 1..3 puts i end </pre>
txt2html.rb test.txt > test.html で変換を実行すると次のようになります。
<p>これは txt2html.rb のテストです。&, ", <, >, は自動的に変換されます。</p> <pre> for i in 1..3 puts i end </pre>
txt2html.rb は HTML のタグが含まれているテキスト文書はきちんと変換してくれません。convert! メソッドの処理を変更すればタグにも対応することができるでしょうが、google 検索すると txt2html という名前のツールはたくさんあります。それらを利用したほうがいいかも知れません。CUI に限らず GUI でもコンピュータにやらせたいことの数だけツールやコマンドを憶える必要がでてきます。出来合いのツールを使いこなすために払う労力よりも自分で小さなツールを作った方がかえって楽なこともあるのではないでしょうか。「他人の作ったツールはできるだけ単機能で使い、自分のツールはできるだけ労力を少なく作りたい。」というのが私の夢です。Ruby を知ってその夢が少し叶いそうな気がして来ました。
これまで述べた HTML ツールでは HTML の出力はボディ部分のみでした。この方がフィルターをかけて処理するのに都合がよいからです。header.rb はその出力にヘッダを付け加えて HTML 文書の形を整えます。
#!/usr/local/bin/ruby print <<EOS <html> <head><title>no title</title></head> <body> EOS while ( line = gets ) puts line end print <<EOS </body> </html> EOS
<<EOS はシェルスクリプトや Perl などでも使われているヒア・ドキュメントです。<<EOS の下の行から次の EOS の行までの間の部分が print メソッドの引数として渡されます。print で出力される行数が多いときはタイプ量を軽減できます。
header.rb は次のように使います。
$ header.rb foo.html > bar.html
また、複数の(ボディ部分だけの) HTML 文書を結合して一つの HTML 文書にまとめることができます。
$ header.rb foo1.src foo2.srb > bar.htmlさらに、anchor.rb や txt2html.rb の出力を最終的に整形するフィルターとして使うこともできます。
$ anchor.rb *.html | grep w3m | header.rb > foo.html
body.rb は header.rb とは逆の処理をするツールです。HTML 文書からボディ部分を抜きだします。簡単なプログラムですので説明は要らないと思います。
#!/usr/local/bin/ruby line = gets while( line !~ /<body/ ) line = gets end line = gets while ( line !~ /<\/body/ ) puts line line = gets end
使い方は次のようにします。
$ body.rb foo.html > foo_body.html
HTML 関係のツールとしては、この他にもリンク集の死んでいるリンクを検出してマークをつけるものとか、ホームページの更新を検出するツール(これは Ruby のアーカイブに既にあるようです)など欲しいものはたくさんあるのですが、著者の知識不足でまだ作れていません。Ruby のプログラミングは比較的楽で、また後で読み返しても暗号文になりにくい特徴があります。個人的な用途に小さいツール(多くはフィルターを)つくってちょこちょこっと問題解決をするのにも Ruby はうってつけだと思います。