[ruby-list:49596] シェルスクリプトの Ruby での置き換え (was Re: コマンドラインで ruby の変数に値を設定する方法)

From: 尾川敏也 <ogw@...>
Date: 2013-09-21 23:24:46 UTC
List: ruby-list #49596
尾川と申します。おはようございます。

1ケ月以上前のメールへのリプライで恐縮です。

UNIX Workstation (実際には現在は Cygwin) 上で、自作のフィルタプログ
ラムを sh や awk と組み合わせて使っているものを Ruby に置き換える件
の結果報告です。

頂いたアドバイス

in [ruby-list:49539] 中田さん wrote:
> それなりのボリュームがあるなら、全体をrubyスクリプトにしても
> いいかもしれません。
>
>   #!/usr/local/bin/ruby -swnla
>   BEGIN {
>     datafile, $param = *ARGV
>     $stdin.reopen(IO.popen("myprog1", "r", in: datafile))
>     $stdout.reopen(IO.popen("myprog2", "w"))
>   }

に触発されて色々勉強しまして、結局下記のようなやり方に落ち着きまし
た。

過去の資産を殆どそのまま機械的に Ruby に置き換えることができ、今後
は Cygwin 無しで済みそうです。

おまけに、従来 awk で書いていた部分は、その気になれば Ruby で非常に
すっきりとコンパクトに書けるようにもなりました。

ちょっと幸せな気分です。あらためて、ありがとうございました。

それにしても、Ruby は楽しいですね。

        *****

myprog1, myprog2, myprog3 は C で書いた外部のフィルタプログラムで、
シェルスクリプトが例えば:

        ----------------- mytest.sh ------------------------
        myprog1 datafile.txt |
        myprog2 |
        awk '{
            #
            # awk による行毎のちょっとした処理
            # 結果は標準出力に書き出す
            #
            print foo
        }' |
        myprog3 -a -b > result.txt
        ----------------------------------------------------

だったとして、これを Ruby では以下のように書けるようになりました。

意味的な見た目も含めてほぼ1対1であるため、元のシェルスクリプトを
コピーして行毎に少し書き直すだけで簡単に Ruby に置き換えられます。

        ----------------- mytest.rb ------------------------
        require_relative "mylib"
        
        "".
        |("myprog1 datafile.txt").
        |("myprog2").
        byline do |sio, n, str| 
          #
          # Ruby による行毎のちょっとした処理
          # 結果は StringIO の sio に書き出す
          #
          sio.puts foo
        end.
        |("myprog3 -a -b").v("result.txt")
        ----------------------------------------------------

require している mylib は、String をモンキーパッチング(と言うので
しょうか?)して、メソッド '|', 'byline', 'v' を追加している自作の
ライブラリで、内容は:

        ----------------- mylib.rb -------------------------
        require "stringio"
        
        class String
        
          # パイプ | を模擬した外部コマンドの実行
          def |(cmdline)
            IO.popen(cmdline, "w+") do |pipe|
              Thread.fork {       # write のブロック防止
                pipe.write self
                pipe.close_write  # read のブロック防止
              }
              pipe.read
            end
          end
        
          # リダイレクト > を v で模擬
          def v(filename)
            File.open(filename, "w") do |f|
              f.puts self
            end
            self
          end
        
          # 行単位で処理した結果全体をまとめて返すイタレータ
          def byline
            result = ""
            sio = StringIO.new result
            n = 0       # 入力行番号
            self.each_line do |str|
              n += 1
              yield sio, n, str
            end
            sio.close
            result      # 各行の処理結果をまとめて返す
          end
        
        end
        ----------------------------------------------------


以上です。

-- 
尾川敏也 ogw@shizuokanet.ne.jp
http://www6.shizuokanet.ne.jp/ogw/

In This Thread