Cのシンボルを置換する方法

This entry was posted by on Thursday, 20 November, 2008

http://q.hatena.ne.jp/1227069551

これについてはhandyな解決策はないだろうと思う(GNU globalでできる、という回答があるけど本当だろうか。globalそんなに真面目に使ったことないからよく知らないけど、ないだろう、という仮定のもとに以下の文章を書く)。ある識別子と同じ字句がコメント内や文字列リテラル内などにないということがあらかじめわかっているなら、ただの文字列置換でもできるのだけど、そうでもないなら少なくともコメントと文字列ぐらいを解釈する程度の簡易なトークナイザは必須で、それぐらい書くのはそれほどしんどいわけでもないだろうが、こういう質問に対して「パーサ書け」という回答をするのはさすがに人を馬鹿にしすぎているというべきだろう。

一瞬だけ思ったのは、cppにうまいことオプションと-Dを渡せばそういう動作にならないか、ということだが当たり前ながら無理のようだった。当然のように#includeや#defineはすべて展開されてしまうし、/* */のコメントは消えてしまう。//は残るけど、その中の文字列はcppの置換対象。

まあ、回答に対する質問者の反応を見る限りでは、つまりsedとかでよかったらしいけれど。

ところで、同じく回答を見ていて知らない人も結構いるのではないかということに気づいたのだが、sed/perl/rubyには-iというフラグがあって非常に便利なのでおすすめしておきたい。この質問の回答者のように、

sed -e ‘s/foo/bar/g’ $i > $i.tmp && mv -f $i.tmp $i

などとしてもよいのだけど、これは次のようにかける。

sed -i.tmp -e 's/foo/bar/g' $i

(私はよく-i.bakとするけど)。-iはファイルをin-placeに更新するというフラグだが、-iフラグには引数を指定することもでき、指定するとin-placeに更新するのだが元のデータも指定した値を末尾につけた名前のファイルとして残す。言葉で書くと煩雑だが、つまり上の例は、

mv $i $i.tmp; sed -e 's/foo/bar/g' $i.tmp > $i

とほぼ同じ動作になるということ。-iでバックアップしつつ変換してしまい、うまいこといったと確認できたらバックアップは削除する、という感じだ。処理に自信があるなら-i単体にしておけば元のファイルは残らないし、中間ファイル名で思い悩む必要がないから気が楽。perlの-nや-p、それに影響を受けたrubyの-nや-pも同じように-iがある。もちろんmvやcpと使えばいいけれど、複数のコマンドの組み合わせではなくてコマンド単体で動くことにはそれなりの価値があって、たとえばfindやxargsと組み合わせやすい。

このフラグはこういうケースですごく便利なので、あらゆるコマンドに付属してくれると素晴らしいことが起こるような気がする。というわけで、任意のフィルタを引数にとり、標準出力に書き出す代わりにin-placeに更新するというinplaceというコマンドもある。あるというか、多分パッケージとして存在するのはFreeBSDだけだろうがRubyで書かれているのでその辺の普通のOSで動くと思う。もっとも、「これはすごく便利に違いない!!」と興奮して入れてみたのだが実際使ってみたことは数えるほどしかないけれど(笑)。

ちなみに、これまた有名な話だがsedはそんなに速くない。バージョンいくつかからだったか忘れたけどGNU sedは真面目に多言語処理をするようになったため急激に速度低下してしまった、という話じゃなかったかと思った。ともあれ一般的なケースではperlの方がよっぽど速いので、sedは存在を忘れた方がいい。どうせちょっと複雑なことをやろうとするとsedスクリプトなんか書いてられない(まあ私はpelすらまともに書く能力を失ったのでrubyでちょこちょこ書きますけれども)。

というわけで何のはなしだったっけ……ああそうそう、sedでもperlでもrubyでもいいけど、-iは覚えて置いて損はないです。

2 Responses to “Cのシンボルを置換する方法”

  1. kou

    確かに! < 私はpelすらまともに書く能力を失った

  2. 向井

    orz