Rubyでワンライナー

まずはPerlのプログラムから

先日、Railsを開発している若者と次のような会話があった。

若者:geditで正規表現を使って「^」を「,」で置換しようとしているのですが(数百行の文字列の行頭にコンマを挿入したいらしい)、うまくいかないんです。Hidemaruだとうまくいったはずなんですが。
私:たぶん、正規表現のHidemaruの独自拡張でできるかもしれないけど、普通はそれじゃ無理だよ。Rubyのワンライナーで置換しちゃえば。

そのことをふと今日思い出して、そもそもワンライナーという言葉とか知っているのかな、今時の若者は、とか思ってしまいました。

と同時に、そういう自分も以前のPerlが全盛の頃だったらワンライナーをよく使っていましたが、今ではほとんど記憶の墓場です(トホホホ)。なので復習がてら、まずはPerlのワンライナーを(思い出しながら)書いてみました。最初はワンライナーではちょっと見づらかったりするので、最初は複数行のスクリプトから。

正規表現的にはPerlでは下のような書き方で、$_という変数内の文字列を置換します($_は後述)。

s/^(.*)$/,\1/"

なので、次のような感じになるでしょうか。

while(<>){
    s/^(.*)$/,\1/;
    print;
}

簡単に説明します。

  1. while(<>)はコマンドラインのスクリプトファイルの後のファイル名をオープンして、文末まで1行ずつ処理する、Perl独自の書き方です。
  2. 1のループで、1行の内容は$_に入っています。
  3. $_ =~ s/^(.*)$/,\1/;」で(Rubyで言うところの破壊的メソッド)置換ができますが、「$_ =~」を省略すると暗黙的に$_が対象になるので、書いてありません。
  4. printで引数を省略すると、$_が出力します。

コマンドラインで、仮にこのスクリプトがtest.plにあり、またtmp.txtに対象の文字列があり、置換後の文字列をtmpp.converted.txtに書き出すとすれば、次のようになるでしょう。

perl test.pl tmp.txt > tmpp.converted.txt

もちろん、これで十分かもしれませんが、Perlにはワンライナーを書くのに便利なオプションがあって、もっと簡潔に書けます。

Perlのコマンドオプション(ワンライナー関係)

  1. -eオプション
    -eオプションに与えられた文字列をスクリプトとして実行します。

    perl -e 'script'
    
  2. -nオプション
    -nオプションをつけると以下のループの内部にスクリプトがあるような動作をします。

     while(<>) {
       script;
       .....
     }
    

    次のように実行します。

    perl -ne 'script' filename
    
  3. -pオプション
    -pオプションをつけると以下のループの内部にスクリプトがあるような動作をします。

     while(<>) {
       script;
       ....
     }continue{
       print $_;
     }
    

    次のように実行します。

    perl -pe 'script' filename
    

    ちなみに、continueブロックはfor文の「for(A;B;C;)」のCにあたり、1回ループが回ったときに実行されるブロックです。

  4. -lオプション
    -lオプションは、n,pオプションと組み合わせて使い、ループの前で、$_をchomp(改行コードの削除)を実施します。

     while(<>) {
       chomp $_;
       script;
       ....
       print $_;
     }
    

    次のように実行します。

     perl -ple 'script' filename
    
  5. -aオプション
    -aオプションは入力された行をsplit(分割)して、@Fという配列にセットします。分割パターンは/\s+/です。

    perl -ane 'print $F[0],"\n"' filename
    
  6. -Fオプション
    -Fオプションは-aオプションで分割する分割パターンを指定します。

    perl -F'/\t/' -ane 'print $F[0],"\n"' filename
    
  7. -iオプション
    これは対象のファイルを破壊的に変えてしまって、元のファイルはバックアップとして保存します。
    -iオプションの後に文字列を指定して、その文字列が拡張子として加えられてバックアップファイルができます。

    perl -i".bak" -pe 's/したがって/従って/g' tmp.txt
    

    tmp.txtファイルに含まれる”したがって”という文字列を”従って”に置換え、オリジナルのファイルをtmp.txt.bakという名前にしてバックアップしてくれます。

オプションにはまだまだありますが、Perl のワンライナーについての個人的なメモあたりを参照ください。

さてさてこれらのオプションの中の「ipe」を使うと

perl -i".bak" -pe "s/^(.*)$/,\1/" tmpp.txt

これで、tmpp.txtの全ての行頭に「,」が挿入され、オリジナルのファイルはtmpp.txt.bakで保存されます。

Rubyだってワンライナーが当然できるだろう

そこで、Rubyでも当然ワンライナーを書けるだろうと思って調べたら、当然ありました。

というか、上記に出ているPerlのオプションはRubyでも同じだし「$_」も同じようなので、私には、覚えなおす必要もなくラッキーです!とすると、こんな感じでできちゃいます。
注意すべきは、ダブルクォーテーションの中に「\」があるので、これをエスケープしているところでしょうか。

それからPerlではs/A/B/の「s」が置換の意味になり、「$_ =~ s/A/B/;」の「=~」で正規表現のマッチングをさせますが、Rubyの場合はsubメソッドを使って置換することと、デフォルトが破壊的メソッドじゃないRubyでは「sub!」で「!」が必要なところあたり注意が必要ですね($_自体が変更されないとダメなので)。

ruby -i'.bak' -pe '$_.sub!(/^(.*)$/, ",\\1")' tmpp.txt

さあ、これで変換終了です。慣れれば、10秒ぐらいで作業が終了しますw

あれ、題名が「Ruby ワンライナー」と言いながら、Rubyのところがあまりにも少ない(>_<)!

終了。