rubyのワンライナーに見る驚きの省略記法
rubyには、省略されたコードが隠されていることがある。その省略されたコードをちゃんと理解しておかないと、rubyの中で何が起こっているのか?見失ってしまう...。調べてみた。
- 一般的なソースコードの中では、可能な限り省略せずに書いた方が良いと思われる。
- きっと、他人を悩ますか、1カ月後の別人の自分が悩む。
- しかし、直接タイプすることが多いワンライナーでは、素早く、簡潔に入力できる省略表現は便利である。
- 無駄に$や!を入力しないので、エスケープの問題で悩むことも少なくなると思われる。
作業環境
rubyコマンドのオプション基本
- テスト用にtest.txtを作った。
cat << EOS > test.txt りんご 200 みかん 100 メロン 500 EOS
- test.txtを出力してみる。
- getsは与えられたtest.txtを1行ずつ読み取る。
- 読み取ったデータは、変数$_に代入される。
- printで$_を出力する。
$ ruby -e 'while gets; print $_; end' test.txt
りんご 200
みかん 100
メロン 500
- -nオプションは、while gets 〜 endループを作る。
$ ruby -ne 'print $_' test.txt
りんご 200
みかん 100
メロン 500
- -pオプションは、そのループの最後にprint $_する。
$ ruby -pe '' test.txt
りんご 200
みかん 100
メロン 500
- 何もrubyコード書いてないのに、-pオプションだけで出力されるなんて素敵!
区切り文字で区切る
- -aオプションは、読み込みながらsplitを実行する。
$ ruby -a -ne 'p $F' test.txt
["りんご", "200"]
["みかん", "100"]
["メロン", "500"]
- でも、区切り文字が空白文字(スペース・タブ・改行*1)以外だと...
cat << EOS > test.txt りんご;200 みかん;100 メロン;500 EOS
- splitはうまく区切れない。
$ ruby -a -ne 'p $F' test.txt
["りんご;200"]
["みかん;100"]
["メロン;500"]
- そんな時は、-Fオプションで区切り文字を指定する。
$ ruby -aF';' -ne 'p $F' test.txt
["りんご", "200\n"]
["みかん", "100\n"]
["メロン", "500\n"]
- 末尾の改行が邪魔な時は、-lオプションを指定する。
$ ruby -alF';' -ne 'p $F' test.txt
["りんご", "200"]
["みかん", "100"]
["メロン", "500"]
- 省略しないと、こんな感じだろうか。
$ ruby -e 'while gets; $F = $_.chop.split(";"); p $F; end' test.txt
["りんご", "200"]
["みかん", "100"]
["メロン", "500"]
- chompでなく、chopだった。
- それから「$\を$/と同じ値に設定し、printでの出力時に改行を付加する」処理もあるそうだ。
入力ファイルへの上書き
- -iオプションを指定すると、ファイルに上書きする。
$ ruby -i -alF';' -ne 'p $F' test.txt $ cat test.txt ["りんご", "200"] ["みかん", "100"] ["メロン", "500"]
- いや、まだ、このテストは続くので上書きは困る。
cat << EOS > test.txt りんご;200 みかん;100 メロン;500 EOS
入力ファイルをバックアップしてから上書き
- そんな時は、-iオプションにバックアップ拡張子を指定することもできる。
$ ruby -i'.back' -alF';' -ne 'p $F' test.txt $ cat test.txt ["りんご", "200"] ["みかん", "100"] ["メロン", "500"] $ cat test.txt.back りんご;200 みかん;100 メロン;500
- test.txt.backにバックアップしてから、test.txtに上書きされるのだ。
- test.txtを元に戻しておく。
$ mv -f test.txt.back test.txt $ cat test.txt りんご;200 みかん;100 メロン;500
BEGINとENDブロック
- 金額の集計をしてみる。
$ ruby -alF';' -ne 'BEGIN{total=0}; total += $F.last.to_i; END{puts total}' test.txt
800
- 上記ワンライナーは以下のコードと同等なのだ。
$ ruby -e 'total = 0; while gets; $F = $_.split(";"); total += $F.last.to_i; end; puts total;' test.txt
- 分かりやすく改行して表現すると、こんな感じ。
$ ruby -e '
total = 0
while gets
$F = $_.split(";")
total += $F.last.to_i
end
puts total
' test.txt
「ん」を含む行を取り出す
- -nオプションを指定して、一般的には以下のように書く。
$ ruby -ne 'print $_ if $_ =~ /ん/' test.txt
りんご;200
みかん;100
- まさしく、上記コードは$_を削除できるケース。
$ ruby -ne 'print if /ん/' test.txt
りんご;200
みかん;100
2〜3行目を取り出す
- $_省略の応用かもしれないけど、範囲指定した行を取り出すこともできる。
$ ruby -ne 'print if 2..3' test.txt
みかん;100
メロン;500
- 本来は、範囲指定の条件式は以下のように書くので...
$ ruby -e '5.times {|n| puts n if (n == 2)..(n == 3)}'
2
3
- よって、省略せずに書くと以下コードになると思う。
- $.には、処理中の行番号が代入されている。
$ ruby -ne 'print $_ if ($. == 2)..($. == 3)' test.txt
みかん;100
メロン;500
みかんをオレンジに置き換える
- -pオプションを指定して、普通に書くと以下のようになった。
$ ruby -pe '$_.sub!(/みかん/, "オレンジ")' test.txt
りんご;200
オレンジ;100
メロン;500
- ところで、例によって、$_はこの場合も省略できる。
- しかも、$_を省略した場合は、sub!でなくsubのみで破壊的メソッドと同等の扱いを受けるのだ!
$ ruby -pe 'sub(/みかん/, "オレンジ")' test.txt
りんご;200
オレンジ;100
メロン;500
- 逆に、$_を省略して、破壊的sub!にしてしまうと、エラーになる。
$ ruby -pe 'sub!(/みかん/, "オレンジ")' test.txt -e:1:in `': undefined method `sub!' for main:Object (NoMethodError)
つまり、$_を省略するなら、破壊的メソッドは使ってはいけない。
$_を省略した時の連続処理
- タブ区切りにして、末尾に円を付加してみる。
- 個別に実行する例:
$ ruby -pe 'gsub(/;/, "\t"); gsub(/(\d+$)/, "\\1円")' test.txt
りんご 200円
みかん 100円
メロン 500円
- 連続して実行する例:
- 最初はgsubだが、2個目は破壊的gsub!にしておく必要がある。
$ ruby -pe 'gsub(/;/, "\t").gsub!(/(\d+$)/, "\\1円")' test.txt
りんご 200円
みかん 100円
メロン 500円
-
- 2個目もgsubだと(破壊的gsub!でないと)、円が出力されない。
$ ruby -pe 'gsub(/;/, "\t").gsub(/(\d+$)/, "\\1円")' test.txt
りんご 200
みかん 100
メロン 500
まとめ
- それでは、なんでもかんでも$_を省略できるかというと、そうではない。
- 例えば、stripなんて使えると便利そうだが、$_を省略するとエラーになる。
$ echo ' hello world! ' | ruby -pe 'strip' -e:1:in `': undefined local variable or method `strip' for main:Object (NameError) $ echo ' hello world! ' | ruby -pe '$_.strip!' hello world!
省略できる場合
自分が試した限り、以下の省略が可能だった。(他にもあるかもしれない)
- sub!、gsub!、chomp!、chop!、print、正規表現との比較で、$_を省略可能。
- 範囲指定の条件式で、$.を省略可能。
-
- sub!、gsub!、chomp!、chop! での省略例($_を省略するなら、破壊的メソッド!は使ってはいけない。)
$ echo ' hello world! ' | ruby -pe '$_.chomp!; $_.gsub!(/^\s+|\s+$/,""); $_.chop!; $_.sub!(/^./){|m| m.upcase}' $ echo ' hello world! ' | ruby -pe ' chomp ; gsub (/^\s+|\s+$/,""); chop ; sub (/^./){|m| m.upcase}' Hello world
-
- print、正規表現の比較 での省略例
$ ruby -ne 'print $_ if $_ =~ /ん/' test.txt $ ruby -ne 'print if /ん/' test.txt りんご;200 みかん;100
-
- 範囲指定の条件式 での省略例
$ ruby -ne 'print $_ if ($. == 2)..($. == 3)' test.txt $ ruby -ne 'print if 2 .. 3 ' test.txt みかん;100 メロン;500
rubyのオプション一覧
- この日記で実際に試したオプションの一覧
rubyコマンド | 実行されるコードまたは動作 |
---|---|
ruby -e 'rubyコード' | rubyコード |
ruby -ne 'rubyコード' | while gets; rubyコード; end |
ruby -pe 'rubyコード' | while gets; rubyコード; print; end |
ruby -a -ne 'rubyコード' | while gets; $F = $_.split; rubyコード; end |
ruby -aF';' -ne 'rubyコード' | while gets; $F = $_.split(";"); rubyコード; end |
ruby -alF';' -ne 'rubyコード' | while gets; $F = $_.chop.split(";"); rubyコード; end |
ruby -ne 'BEGIN{total=0}; rubyコード; END{puts total}' | total=0; while gets; rubyコード; end; puts total |
ruby -i -ne 'rubyコード' test.txt | test.txtを読み込んで、rubyコードの処理をして、test.txtに上書き |
ruby -i'.back' -ne 'rubyコード' test.txt | test.txtを読み込んで、rubyコードの処理をして、test.txt.backにバックアップしてから、test.txtに上書き |
getsと連携する変数
- この日記で実際に試した省略可能な組込変数
$_ | getsで読み込んだ内容が1行ごとに代入される |
---|---|
$. | getsで読み込み中の行番号が代入されている |
参考ページ
以下のページがたいへん参考になりました。素晴らしい情報に、感謝です!
- ruby ワンライナーの使い方まとめ - それマグで!
- Rubyでメソッド一覧を確認するmethodsメソッドが便利 - タブレット上のcron
- Ruby intro: syntax 1 (Japanese)
- [Ruby]putsとprintの挙動の違いを確かめてみる : THINK PINK
- 組み込み変数
エスケープ問題の解決
$ echo "私は'zarigani'です。" | ruby -pe
bashとrubyの連携に見るエスケープな悩み - ザリガニが見ていた...。
- という訳で、前回の問題の最もシンプルな表現は、こうなる。
$ echo "私は'zarigani'です。" | ruby -pe "gsub(/'/,'')"
私はzariganiです。
- 省略記法によって、ダブルクォート内にそのままrubyコードを書けるようになった。