Rubyワンライナー入門

先日仕事中、一瞬の隙も見逃さずに情報収集できるRubyワンライナーとスクリプトというネタエントリを書いたのだが、その際Rubyワンライナーをもう少しまともに理解したいと思ったので入門してみた。手元のRubyは1.9.2

はじめに

Rubyワンライナーは、rubyコマンドに色々なオプション(-eや-nなど)をつけて実行する。各オプションの網羅的な解説が見たければ、コマンドラインでは$ man rubyで閲覧できるし、webではるりまサーチで参照できる。ただ、これらは網羅的すぎるので、以下よく使うオプションとその周辺情報についてまとめた。

-e

一番基本的なオプション。スクリプトを実行する。(ちなみに他のオプションと組み合わせる時は必ず最後に記載する)

例: hogeと表示

$ ruby -e 'puts "hoge"'

-n

プログラム全体がwhile gets ... endというループで囲まれているように動作する。ちょっとややこしい。

例: hoge.txtとfuge.txtの内容を表示

$ ruby -ne 'puts $_' hoge.txt fuge.txt
理解するには

Kernel.#gets、ARGF、$_について理解する必要がある

Kernel.#gets

ARGFから一行読み込んで、それを返します。...読み込んだ文字列は組み込み変数 $_ にもセットされます。...
http://rurema.clear-code.com/1.9.2/method/Kernel/m/gets.html

つまり、以下は同義

$ ruby -ne 'puts $_' hoge.txt fuge.txt
$ ruby -e 'while gets; puts $_; end' hoge.txt fuge.txt

ざっくりいうと、以下のような流れになる

hoge.txtとfuge.txtを連結した仮想ファイル(ARGF)をセット

ARGFから1行読み込み、$_にセット。読み込めた場合は$_を出力してループ

ちなみに

上記では出力の部分をputs $_としているが、これはKernel.#printで代替できる。

Kernel.#print

引数を順に標準出力 $stdout に出力します。引数が与えられない時には変数 $_ の値を出力します。...
http://rurema.clear-code.com/1.9.2/method/Kernel/m/print.html

つまり、以下は全て同義

$ ruby -ne 'print' hoge.txt fuge.txt
$ ruby -ne 'puts $_' hoge.txt fuge.txt
$ ruby -e 'while gets; puts $_; end' hoge.txt fuge.txt

-p

nオプションとほぼ同じ。nオプションと同じくプログラム全体をwhile gets...endループするが、さらに各ループの最後にprintする。つまり、printが省略できるということ。なので、以下は全て同義。

$ ruby -pe '' hoge.txt fuge.txt
$ ruby -ne 'print' hoge.txt fuge.txt
$ ruby -ne 'puts $_' hoge.txt fuge.txt
$ ruby -e 'while gets; puts $_; end' hoge.txt fuge.txt

$_を破壊的メソッドで変換して出力するような場合に便利

例: hoge.txtとfuge.txtの内容を全て大文字にして表示

$ ruby -pe '$_.upcase!' hoge.txt fuge.txt

-a

オートスプリット。nかpと一緒に使う。各ループの先頭で$F = $_.splitをを実行する。

例: カレントディレクトリの情報を配列に変換して表示

$ ls -laF | ruby -ane 'p $F'
-F

$;(入力フィールドセパレータ。splitのデフォルト区切り文字)を設定

例: 環境変数PATHを:で分割して改行表示

$ echo $PATH | ruby -F: -ane 'puts $F'

-r

ライブラリ読み込み。

例: yahooのhtmlを取得

$ ruby -r open-uri -e 'open("http://www.yahoo.co.jp") {|f| puts f.read}'

ちなみに、複数のライブラリを読み込みたい場合は-r を複数書く。

例: facebookapiからマークザッカーバーグJSONを取得しハッシュに変換

$ ruby -r open-uri -r json -e 'open("http://graph.facebook.com/zuck") {|f| p JSON.parse(f.read)}'

BEGIN, END

オプションでは無いのだが、普段あまり使わないRubyの構文BEGINとEND(begin...endとは全くの別物)がワンライナーだと結構使われるようだ。

例: 全行が整数のnumber.txtの合計値を表示

$ ruby -ne 'BEGIN{$sum = 0}; $sum += $_.to_i; END{puts $sum}' number.txt 

BEGINとENDをつけないと、毎行読み込むごとに$sum = 0; と puts $sumが実行されてしまうので意図した結果にならない

BEGIN

初期化ルーチンを登録します。BEGINブロックで指定した文は当該ファイルのどの文が実行されるより前に実行されます。...
http://doc.ruby-lang.org/ja/1.9.2/doc/spec=2fcontrol.html#BEGIN

END

「後始末」ルーチンを登録します。END ブロックで指定した文はインタプリタが終了する時に実行されます。Ruby の終了時処理について詳しくは 終了処理を参照してください。...
http://doc.ruby-lang.org/ja/1.9.2/doc/spec=2fcontrol.html#END

書籍プログラミング言語Rubyによると、この構文はawkを受け継いだものらしい。以下のサイトが参考になりそう。

Ruby one-liners
AWKのススメ « クックパッド開発者ブログ

終わり

とりあえずRubyワンライナー入門した!

仕事中、一瞬の隙も見逃さずに情報収集できるRubyワンライナーとスクリプト

情報収集はビジネスマンとしての基本である。しかし普段シェルで作業する者としては、毎回ブラウザを立ち上げる時間すら不毛である。よって、シェルから一瞬で情報収集できるRubyワンライナーを書いた。

上からYahoo, はてな, Naverまとめ, 2ちゃん

$ ruby -r open-uri -e 'open("http://www.yahoo.co.jp").read.scan(/topics.+?>([^<]+?)</) {|m| puts m}'
$ ruby -r open-uri -e 'open("http://b.hatena.ne.jp/hotentry").read.scan(/entry-link.+>(.+?)</) {|m| puts m}'
$ ruby -r open-uri -e 'open("http://matome.naver.jp/").read.scan(%r{matomename.+?-->(.+?)<!-}m) {|m| puts m}'
$ ruby -r open-uri -e 'open("http://uni.2ch.net/newsplus/").read.scan(/<a.+?>\d+?:\s(.+?)</) {|m| puts m}' | nkf -w

あれ?

普通にブラウザ開いた方が早いw

なので

コマンド一発で情報収集できるスクリプトを書いた

news
#!/usr/bin/env ruby
 
require 'open-uri'
require 'nkf'
 
defined_sites = {
  'yahoo' => {
    'url' => 'http://www.yahoo.co.jp',
    'regexp' => /topics.+?>([^<]+?)</
  },
  'hatena' => {
    'url' => 'http://b.hatena.ne.jp/hotentry',
    'regexp' => /entry-link.+>(.+?)</
  },
  'naver' => {
    'url' => 'http://matome.naver.jp/',
    'regexp' => /matomename.+?-->(.+?)<!--/m
  },
  '2ch' => {
    'url' => 'http://uni.2ch.net/newsplus/',
    'regexp' => /<a.+?>\d+?:\s(.+?)</
  }
}
 
class Site
  attr_accessor :url, :regexp
 
  def initialize
    yield self
  end
 
  def fetch
    open(@url) do |f|
      f.read.scan(@regexp) {|m| puts NKF.nkf('-w', m.join)}
    end
  end
end
 
site_names = ARGV.empty? ? defined_sites.keys : ARGV
sites = []
site_names.each do |site_name|
  raise 'not defined' unless defined_sites.keys.include? site_name
 
  sites << Site.new do |s|
    s.url = defined_sites[site_name]['url']
    s.regexp = defined_sites[site_name]['regexp']
  end
end
 
sites.map(&:fetch)

こいつに実行権限を与えて、pathを通して、newsコマンド

$ news

東電社長 時効主張せずと明言
鳩山元首相、招かれ訪中へ
高2自殺 前日に平手30-40発?
JTB 企業の体力測定事業参入
魔女狩りで禁固20年 ネパール
本田にミランがオファー準備
安藤美姫がトヨタ自動車退社
金爆・鬼龍院のNSC芸人時代
今日の話題(27件)
一番福へ全速力
...

キタ!


yahooとhatenaだけ表示したければ

$ news yahoo hatena


対象サイトを追加したければ、ハッシュ(defined_sites )に追加すればOK!
これで一瞬の隙も見逃さずに情報収集する「できるビジネスマン」になれる!

※注 ネタです

※追記 Rubyワンライナーについてまとめた
Rubyワンライナー入門

RackとERBを使って、超シンプルなRubyのWebアプリを作る

Railsで使われているRackとERB。いつもRailsにお任せだから、たまにはRailsの皮をはいで生で触ってみたい!ということで、超基本的な部分(通常表示, リダイレクト, not found)だけいじったのでメモ。

・/にアクセスすると、/hogeにリダイレクトされる
・/hogeは変数nameとsexに格納した値をerbで表示する
・それ以外のURLにアクセスされたら404エラーを返す

という代物を作る

config.ru

#coding: utf-8                                                                                                                                 

run lambda {|env|
  request = Rack::Request.new(env)

  case request.path
  when '/' 
    Rack::Response.new {|r| r.redirect("/hoge")}
  when '/hoge'
    name, sex = 'maeharin', 'man'

    html = ERB.new(<<-EOF).result(binding)
      <html>
      <head><meta charset="utf-8"></head>
      <body>
      私の名前は<%= name %>。性別は<%= sex %>です。
      </body>
      </html>
    EOF

    Rack::Response.new(html)
  else
    Rack::Response.new("not found", 404)
  end 
}

サーバー起動

config.ruがあるディレクトリで

$ rackup

localhost:9292にアクセス

動いた!シンプルで素敵すなあ。色々いじってみよう。

※RackとERBの詳細は以下の記事などを参考。
第23回 Rackとは何か(1)Rackの生まれた背景:Ruby Freaks Lounge|gihyo.jp … 技術評論社
Rubyist Magazine - 標準添付ライブラリ紹介 【第 10 回】 ERB

Rubyの多重代入におけるto_aとto_aryの挙動

先日のエントリRubyの多重代入についてふれたのだが、教科書によってto_aryが呼ばれると書いてあったりto_aが呼ばれると書いてあったりで、なにが本当なのか分からなくなった。

回答らしきもの

調べてみたら、こちらに回答らしきものがあった。

OK, to_a means an explicit array conversion, so it should not be used
for implicit conversion a in "*y = x". On the other hand, I consider
"*x" as a form of explicit conversion, i.e. shorthand for "*(x.to_a)",
so that the current 1.9 behavior is intentional.


matz.

http://bugs.ruby-lang.org/issues/show/1393

どうやら、Ruby1.9においては、暗黙的なコンテキストではto_aryが呼ばれ、明示的なコンテキストではto_aが呼ばれるようだ。

1.9.2で試してみた

class C                                                                                                                                                                                              
  def to_ary; [1,2]; end
  def to_a; [3,4]; end
end
c = C.new
 
a, b = c # 暗黙的
d, e = *c # 明示的
 
puts a, b # 1,2
puts d, e # 3,4

確かに暗黙的なコンテキスト(a,b = c)ではto_aryが呼ばれており、明示的なコンテキスト(d,e = *c)ではto_aが呼ばれていることが分かる。ただ、これはto_aryとto_aの両方が定義されている場合の挙動であり、片方の場合には挙動が違った。上記においてto_aryのみを定義した場合、明示的コンテキストにおいてもto_aryが呼ばれる(to_aの代わりに)。しかしto_aのみを定義した場合は、暗黙的コンテキストにおいては通常の多重代入が行われる(to_aryの代わりにto_aが呼ばれることはない)。結構ややこしい。

1.8.7でも試してみた。

class C                                                                                                                                                                                              
  def to_ary; [1,2]; end
  def to_a; [3,4]; end
end
c = C.new
 
a, b = c # 暗黙的
d, e = *c # 明示的
 
puts a, b # 1,2
puts d, e # 1,2

さらにややこしいことに、1.8.7の場合、1.9.2とは挙動が違った。to_aryとto_a両方が定義されていると、暗黙的でも明示的でもto_aryが呼ばれる。片方だけ定義された場合でいうと、to_aryのみが定義されている場合、暗黙的でも明示的でもto_aryが呼ばれる。一方to_aのみが定義されている場合は、暗黙的コンテキストでは通常の多重代入が行われ、明示的コンテキストにおいてはto_aが呼ばれる。


まとめ

すごく混乱したので、まとめておく。あまりこの違いで悩むことは無いだろうけど。。。

Ruby バージョン 暗黙的コンテキスト 明示的コンテキスト
1.9.2 to_ary to_a(なければto_ary)
1.8.7 to_ary to_ary(なければto_a)

Rack::Responseオブジェクトを多重代入すると、暗黙的にfinishが呼ばれる

環境:ruby-1.9.2、rack-1.4.1

RAILSCASTのRackの章を見ていたら、以下のような構文が出てきて、不思議に思った。

def call(env)
  Rack::Response.new("hoge")
end

Rackアプリの呼び出し側(例えば、lintミドルウェア)では、このように書かれている

status, headers, @body = @app.call(env)

上記の場合、@app.call(env)を評価した結果はRack::Responseオブジェクトであるはず。なのに、それを多重代入できるのはなぜか?配列じゃないといけないのでは?

多重代入の仕様が怪しいので、調べる。

4.5.5.3 複数の左辺、1 個の配列の右辺

左辺は複数あるが、右辺は 1 つしかない場合、Ruby はその右辺を代入すべき値のリストに展開しようとす
る。右辺が配列なら、Rubyは配列を展開し、個々の要素が独自の右辺になるようにする。右辺が配列でなくても、to_aryメソッドを実装する場合は、to_aryを呼び出して配列を作ってから、その配列を展開して代入する。

プログラミング言語 Ruby P102

どうやら、上記のような状況下で多重代入を行うと、右辺のオブジェクトにto_aryメソッドがある場合、その実行が行われるようだ。ということは、おそらくRack::Responseクラスのインスタンスメソッドにto_aryが定義されているはず。Rackのソースコードを見てみる。

# rack-1.4.1/lib/rack/response.rb
def finish(&block)                                                                                                                                                                                          
  @block = block

  if [204, 205, 304].include?(status.to_i)
    header.delete "Content-Type"
    header.delete "Content-Length"
    [status.to_i, header, []] 
  else
    [status.to_i, header, self]
  end 
end 
alias to_a finish           # For *response
alias to_ary finish         # For implicit-splat on Ruby 1.9.2

to_aryメソッドはfinishのエイリアスになっていた!なので、Rack::Responseオブジェクトが暗黙的に多重代入されると、finishメソッドの実行結果が代入されるということになる。なるほどー!

※追記
Rubyの多重代入におけるto_aとto_aryの挙動について書いた

RubyのFile.expand_path('相対パス', __FILE__)の意味

RailsなどのRubyライブラリのソースコードを見ていると、よく

File.expand_path('相対パス', __FILE__)

という一文を目にする。ちょっと調べてみた。

File.expand_pathとは

riコマンドで調べてみる

$ ri File.expand_path

(from ruby core)
------------------------------------------------------------------------------
  File.expand_path(file_name [, dir_string] )  ->  abs_file_name

------------------------------------------------------------------------------

Converts a pathname to an absolute pathname. Relative paths are referenced
from the current working directory of the process unless
dir_string is given, in which case it will be used as the
starting point. The given pathname may start with a ``~'', which expands to
the process owner's home directory (the environment variable HOME must be set
correctly). ``~user'' expands to the named user's home directory.

       File.expand_path("~oracle/bin")           #=> "/home/oracle/bin"
       File.expand_path("../../bin", "/tmp/x")   #=> "/bin"

相対パス絶対パスに変換した文字列を返す
・第2引数を指定しない場合「プロセスのカレントワーキングディレクトリ」を相対パスの基準にする
・第2引数を指定した場合「第2引数で指定したディレクトリ」を相対パスの基準にする

検証用ディレクトリ構造

検証用に、以下のディレクトリを作った。a.rbからlib/を参照することを想定して、検証する。

/
└── Users/
    └── hidenorimaehara/
        └── xxx/
            └── yyy/
                └── a.rb
                └── lib/

File.expand_path('./lib')

まずは第2引数を指定しないでやってみる。

a.rb

プロセスのカレントワーキングディレクトリを確認するため、Dir.pwdの結果も表示することにする。

puts Dir.pwd
puts File.expand_path('./lib')
yyyディレクトリで実行
$ ruby a.rb
/Users/hidenorimaehara/xxx/yyy
/Users/hidenorimaehara/xxx/yyy/lib
xxxディレクトリで実行
$ ruby ./yyy/a.rb
/Users/hidenorimaehara/xxx
/Users/hidenorimaehara/xxx/lib

プロセスのカレントワーキングディレクトリを相対パスの基準として絶対パスに変換していることが分かる。つまり、コマンドを実行するディレクトリによって結果が変わる。

File.expand_path('./lib', '/Users/hidenorimaehara/xxx/yyy/')

次に第2引数を与えてみる。yyyディレクトリの絶対パスを第2引数に指定してみる。

a.rb

さきほどのa.rbを以下のように変更。

puts Dir.pwd
puts File.expand_path('./lib', '/Users/hidenorimaehara/xxx/yyy/')
yyyディレクトリで実行
$ ruby a.rb
/Users/hidenorimaehara/xxx/yyy
/Users/hidenorimaehara/xxx/yyy/lib
xxxディレクトリで実行
$ ruby ./yyy/a.rb
/Users/hidenorimaehara/xxx
/Users/hidenorimaehara/xxx/yyy/lib

第2引数に指定したディレクトリを基準として相対パスを解析していることがわかる。

__FILE__

ここで一旦脇道にそれて、__FILE__について調べる。__FILE__は、現在のソースファイル名を返す変数。実際にどのような値を返しているのか調べてみる。

a.rb
puts __FILE__
yyyディレクトリで
$ ruby a.rb
a.rb
xxxディレクトリで
$ ruby ./yyy/a.rb
./yyy/a.rb

コマンドを実行したディレクトリからの相対パスでファイル名を返しているようだ。(こちらによると相対を返すか絶対を返すかは状況によって変わるらしい)

File.expand_path('./lib', __FILE__)

となるとFile.expand_pathの第2引数に__FILE__を指定すれば、現在のソースファイルを基点とできる。第1引数に'./lib'、第2引数に__FILE__を指定してみる。

a.rb
puts Dir.pwd
puts File.expand_path('./lib', __FILE__)
yyyディレクトリで
$ ruby a.rb
/Users/hidenorimaehara/xxx/yyy
/Users/hidenorimaehara/xxx/yyy/a.rb/lib
xxxディレクトリで
$ ruby ./yyy/a.rb 
/Users/hidenorimaehara/xxx
/Users/hidenorimaehara/xxx/yyy/a.rb/lib

ありゃ
./では、a.rb(ファイル名)自体もパスに入ってしまっている。

File.expand_path('../lib', __FILE__)

ということで、第1引数に../libにしてみる。

a.rb
puts Dir.pwd
puts File.expand_path('../lib', __FILE__)
yyyディレクトリで
$ ruby a.rb
/Users/hidenorimaehara/xxx/yyy
/Users/hidenorimaehara/xxx/yyy/lib
xxxディレクトリで
$ ruby ./yyy/a.rb 
/Users/hidenorimaehara/xxx
/Users/hidenorimaehara/xxx/yyy/lib

できた!

以下のように使う

こうすれば、プロセスのカレントワーキングディレクトリがどこであっても、相対的な位置関係を参照できる

# このソースファイルと同ディレクトリにあるb.rbをrequire
require File.expand_path('../b', __FILE__)

# このソースファイルと同ディレクトリにあるlibディレクトリをrequireのロードパスに追加
$: << File.expand_path('../lib', __FILE__)

rbenvのメモ

備忘のためメモ。必要に応じて編集予定。

ヘルプ

$ rbenv -h

主要コマンドの説明はこれで

特定のrbenvコマンドのヘルプを表示

$ rbenv help <コマンド>

現在のRubyバージョンを表示

$ rbenv version

rbenvが管理している全てのRubyバージョンを表示

$ rbenv versions

インストールできるversionを表示

$ rbenv install

特定のversionをインストール

$ rbenv install 1.9.3-rc1
$ rbenv rehash

versionの切り替え(global)

$ rbenv global 1.9.3-rc1

versionの切り替え(local)

$ rbenv local 1.9.3-rc1

すると、そのディレクトリに.rbenv-versionというファイルが作成される

ローカル設定を解除したい時は

$ rbenv local --unset

詳しくは

rbenv help local