実は、先に示したコード
File.open(filename) do |ifile|
content=ifile.read
end
File.open(filename+".bak","w") do |ofile|
ofile.write(content)
end
はちゃんと動きません(エラーになります)。
これはローカル変数のスコープの問題です。ローカル変数 content の 有効範囲(スコープ)は、その変数が最初に宣言(代入と同時に)された 関数やメソッドやブロックの中、に限定されます。
案1: トップレベル(ブロックや関数に含まれないエリア)で予め宣言。
content=nil # 値は何でもいい(参照されないので)
File.open(filename) do |ifile|
content=ifile.read
end
# ... 後半は上と同じなので略
案2:open 式(ブロック付き)の値を変数に代入
content=File.open(filename) do |ifile|
ifile.read
end
# ...
ブロック内での代入を行わない。代わりに、
ブロック内で最後に評価した式 ifile.read(ここではこれが唯一の式だが)が、ブロックの値になる。
File#.open を含めて、ブロック呼び出しによるopenメソッド等は、 マニュアルによると「 ブロックの実行が終了すると、ファイルは自動的にクローズされます。ブロックの実行結果を返します。」とあり、 ブロックの値が open メソッドそのものの値となるので、
それを上記のように(ブロックの外側で)変数に代入できる。
この変数はトップレベル全体がスコープになる。
なお、ブロック内のコードがこのぐらいのボリュームだと、
改行する必要もなく、do end
でなく { }
で囲むほうが すっきりしたコードになるだろう。
content=File.open(filename) {|f| f.read}
# 範囲が狭いときにはブロック変数も短い名前にするのが自然
こういう過程を経て、 上から下へ、
変数を通じて値を受け渡ししていく逐次的なプログラム書法
から卒業して、
なるべく関数的な書き方を書くことを学んでいくといいだろう。