Rubyでシェル変数を使う方法には、Rubyプログラムの引数として渡すか、シェル変数を環境変数にするかの2通りがある。

引数として渡す場合、RubyではARGVを使って値を取得する。

$ param1="p1"
$ param2="p2"
$ ruby -e 'puts ARGV[0], ARGV[1]' $param1 $param2
p1
p2

環境変数にする場合、RubyではENV['変数名']で値を取得する。

# 対象プロセス実行中のみ環境変数が有効になるようにする場合
$ param1="p1" param2="p2" ruby -e 'puts ENV["param1"], ENV["param2"]'
p1
p2

# 以降、現在のプロセスと子プロセスで環境変数が有効になってもいい場合
$ export param1="p1"
$ export param2="p2"
$ ruby -e 'puts ENV["param1"], ENV["param2"]'
p1
p2

引数として渡す方が簡単な気もするが、引数に渡したファイル名でARGFを使いたい場合は、引数をファイル名以外に渡せない。ARGFを使うことをあきらめ、3つ目の引数にファイル名を渡してFile.open(ARGV[2])のように扱うしかない。しかし、環境変数を利用してRubyでシェル変数を使う方法を採用すると、引数に渡すものがなくなるので、ファイル名を唯一の引数として指定できる。

# 引数でシェル変数を渡す場合、ARGFは使えない
# ruby -e 'puts ARGF.read, ARGV[0], ARGV[1]' $param1 $param2 test.txt
$ ruby -e 'puts File.open(ARGV[2]).read, ARGV[0], ARGV[1]' $param1 $param2 test.txt

# 環境変数でシェル変数を渡す場合、ARGFも使える
$ param1="p1" param2="p2" ruby -e 'puts ARGF.read, ENV["param1"], ENV["param2"]' test.txt

ARGFを使えるようになると、Rubyの-iオプションも使えるようになる。ファイルの内容を置換等して編集後、そのままファイルを保存したい場合、in-placeモードでRubyを実行する。Rubyワンライナーで行単位で決まったパターンで置換編集する場合はARGFやFileクラスを使う必要がないが、パターンを外部からパラメータとして渡したい場合や、複数行に渡る正規表現の置換をするために全行を一度に取得する場合は、ARGF等を使わなければならない。

例えばtest.txtの"should be "という文字列を取り除きたいという要件であれば、一行ずつ置換していけばいいので、ARGFやFileクラスの話は登場せず、in-place置換は簡単にできる。

$ cat test.txt
[start]
line1 should be replaced
line2 should be replaced
line3 should also be replaced
[end]

$ ruby -pi -e 'gsub(/should be /,"")' test.txt

しかし、"should be "という文字列自体を引数として渡したいとなると対応できなくなる。"should be"もファイルとして見られてしまうからだ。

$ ruby -pi -e 'gsub(/#{ARGV[1]}/,"")' test.txt 'should be '
-e:1:in `gets': No such file or directory @ rb_sysopen - should be  (Errno::ENOENT)
        from -e:1:in `gets'
        from -e:1:in `<main>'

File.openで書くと置換自体はできるが、in-place編集はできない。

$ ruby -i -e 'puts File.open(ARGV[0]).read.gsub(/#{ARGV[1]}/,"")' test.txt 'should be '
[start]
line1 replaced
line2 replaced
line3 should also be replaced
[end]
$ cat test.txt
[start]
line1 should be replaced
line2 should be replaced
line3 should also be replaced
[end]

環境変数を使ってRubyに渡す引数をファイル名だけにし、ARGFを使うとin-place編集できる。

$ pat='should be ' ruby -i -e 'puts ARGF.read.gsub(/#{ENV["pat"]}/,"")' test.txt
$ cat test.txt
[start]
line1 replaced
line2 replaced
line3 should also be replaced
[end]

もう一つの例として、複数行に渡る正規表現の置換をする場合も見てみる。test.txtの[start]と[end]で囲まれた箇所を標準入力で渡した内容で書き換えたい。どんなファイル名でも、またどんな置換箇所の識別子でも対応できるようにするため、ファイル名および[start]と[end]はRubyの外から渡さなければいけないとする。こちらも先ほどと同じく、File.openを使ってもin-place編集できないが、ARGFを使うと可能になる。ARGFを使うため、環境変数で変数をRubyに渡さなければならない。

$ echo -e 'line1 replaced\nline2 replaced\nline3 replaced' | ruby -i -e 'puts File.open(ARGV[0]).read.gsub(/(#{ARGV[1]})(?:.*|\n)+?(#{ARGV[2]})/,"\\1\n#{STDIN.read}\\2")' test.txt '\[start\]' '\[end\]'
[start]
line1 replaced
line2 replaced
line3 replaced
[end]
$ cat test.txt
[start]
line1 should be replaced
line2 should be replaced
line3 should also be replaced
[end]

$ echo -e 'line1 replaced\nline2 replaced\nline3 replaced' | start='\[start\]' end='\[end\]' ruby -i -e 'puts ARGF.read.gsub(/(#{ENV["start"]})(?:.*|\n)+?(#{ENV["end"]})/,"\\1\n#{STDIN.read}\\2")' test.txt
$ cat test.txt
[start]
line1 replaced
line2 replaced
line3 replaced
[end]