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です。
解決に至った回答のみポイントを付与させていただきます。
宜しくお願い致します。

回答の条件
  • 1人5回まで
  • 登録:
  • 終了:2013/03/25 17:20:04
※ 有料アンケート・ポイント付き質問機能は2023年2月28日に終了しました。

回答2件)

id:vow No.1

回答回数21ベストアンサー獲得回数9

ポイント150pt

コマンド自体(特に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 とか。

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

  • find | xargs は全く安全でないし不能率です。要注意。
  • sedでは1回以上のマッチは+ではなく\+です。
id:vow

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

2013/03/18 22:39:35
id:xptree

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

2013/03/23 17:03:46
id:JULY No.2

回答回数966ベストアンサー獲得回数247

ポイント150pt

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' {} \;

としても同じです。

id:xptree

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

2013/03/23 17:03:53
  • id:tezcello
    > ファイルの文字コードはSJISです。
    Linux 上で Shift_JIS を使う事がそもそもの原因でしょう。
    16進で直接指定してはいかが?

この質問への反応(ブックマークコメント)

「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

回答リクエストを送信したユーザーはいません