人力検索はてな
モバイル版を表示しています。PC版はこちら
i-mobile

linux sedコマンドに関する質問です。
ディレクトリを再帰的に下りてファイル内の文字列を一括置換するために以下のコマンドがあります。
例:日本をJapanに置換する場合
find . -type f | xargs sed -i "s/日本/Japan/g"
これは正常に日本→Japanに置換できるのですが

ユーザをUserに置換する場合次のエラー文が表示されます。
sed: -e expression #1, char 17: unterminated `s' command
コマンドは以下です。
find . -type f | xargs sed -i "s/ユーザ/User/g"

ウォッチをWatchに置換する場合
find . -type f | xargs sed -i "s/ウォッチ/Watch/g"
以下のエラーが表示されます。
sh: -c: line 0: unexpected EOF while looking for matching ``'
sh: -c: line 1: syntax error: unexpected end of file

マルチバイトの文字によってエラーになるようです。
ユーザの場合は『ー』が原因のようです。
試しに以下のようにしてもエラーは解決されませんでした。
find . -type f | xargs sed -i "s/ユ.+ザ/User/g"

どうすれば全てのマルチバイトを正常に置換できますでしょうか?

ファイルの文字コードはSJISです。
解決に至った回答のみポイントを付与させていただきます。
宜しくお願い致します。

●質問者: xptree
●カテゴリ:ウェブ制作
○ 状態 :終了
└ 回答数 : 2/2件

▽最新の回答へ

1 ● ghost
●150ポイント

コマンド自体(特にsed)がマルチバイト対応の実装であることを前提としても尚、ファイル名のロケールとファイルの内容のロケールがズレている状態は、基本的に全てのコマンドが不得意とする領域です。よほど注意して扱わない限り危険は残ります。

まず、シェルスクリプト(あるいはコマンドライン)のロケールはファイル名のロケールに一致しているので、そのままでは標的ファイルの内容のロケールであるところのsedスクリプトは書けません。たまに動くことがあるのはまぐれです。どれだけクォートしても安全にはなりません。確実を期すならsedスクリプトは全て単体の別ファイルに保存するべきです。

どうしてもシェルスクリプトのインラインで済ませたいなら、ものすごく気持ち悪いですが、その場でiconvするくらいしか逃げ道はありません。

#!/bin/sh
iconv -t SHIFT_JIS << @EOF | find . -type f -exec env LC_CTYPE=ja_JP.SHIFT_JIS sed -i -f - -- '{}' '+'
s/ほげ/もげ/g
@EOF

この場合にはシェルスクリプトは SHIFT_JIS ではなくファイル名の (つまりシステム標準の) ロケールで保存します。

しかしこうすると、今度はsedから見てファイル名のロケールが狂っているのです。つまり標的のファイル名にマルチバイト文字があった場合には、うまく動かないケースがあり得ます。これを回避するには、ファイルをパイプ経由で引き渡すようにしてファイル名をsedに見せなくするか、あるいは一時的にファイルを安全な名前にリネームしてから引き渡すか、そういうラッピングをしなければなりません。

なお今時の大半のシステムでは使用するロケール(ここではja_JP.SHIFT_JIS)のデータを事前生成する必要がある点に注意してください。この作法はOSによって異なります。例えばDebian系なら dpkg-reconfigure locales とか。

なおロケールとは別件として、


ghostさんのコメント
書き忘れましたが、例示のスクリプトそのままだと find -exec が複数回に分割された場合にコケるので、ファイル数がむやみに多い場合はご注意を。

xptreeさんのコメント
ありがとうございました。 とても参考になりました。 ポイントを付与させてください。

2 ● JULY
●150ポイント

Windows に移植した物には --ctype=SJIS というのが使える物があるようですが、
GNU sed with Oniguruma (Onigsed)
Linux のディストリビューションに含まれる物には、こういった機能は無いようです。

で、sed の代わりに ex コマンドを使えば出来ます。

find . -type f -exec ex -s '+e ++enc=cp932' '+% s/ユーザ/user/g' '+wq' {} \;

ただし、この ex コマンドは vim に含まれる ex コマンド(古くからある ex コマンドではない)でないと正しく動かないと思います("++enc=cp932" の部分を解釈出来るのは vim に含まれる ex コマンドじゃないと、たぶん無理)。今時の Linux ディストリビューションであれば、ほぼ間違いなく、vim 由来の ex コマンドだと思います。

もし、はっきり分からなければ、ex を vim -e に置き換えて、

find . -type f -exec vim -e -s '+e ++enc=cp932' '+% s/ユーザ/user/g' '+wq' {} \;

としても同じです。


xptreeさんのコメント
ありがとうございました。 とても参考になりました。 ポイントを付与させてください。
関連質問

●質問をもっと探す●



0.人力検索はてなトップ
8.このページを友達に紹介
9.このページの先頭へ
対応機種一覧
お問い合わせ
ヘルプ/お知らせ
ログイン
無料ユーザー登録
はてなトップ