sed・grepで濁点と改行をまともに扱う方法

#sed 's/^/-/g'			# 行頭に-を付加する
#sed 's/$/-/g'			# 行末に-を付加する
#sed 's/\(xxxx\)/[\1]/g'	# xxxxを[]で囲う
#sed 's/xxxx/oooo/g'		# xxxxをooooに置き換える
  • 必要なコマンドラインのコメントマーク#を削除して、便利に使うつもりでいた。
  • さらに、これは便利と思い、気を良くしてgrepバージョンも作って喜んでいた。
grep -i 'xxxx'
#-i	大文字と小文字を区別しない
#-v	パターンに一致しない行を表示する
#-n	パターンに一致した行のファイル内での行番号を表示する
#-c	パターンに一致した行の行数のみを出力する
#-b	パターンに一致した行の先頭からのバイト数を表示する
  • ところが、自分は生粋の日本人。OSXもアプリケーションも日本語環境で利用している。一つ問題が発生した...。
sed 's/\(folder\)/[\1]/g'  #正常に機能する
sed 's/\(フォルダ\)/[\1]/g'  #置き換えてくれない

grep -i 'folder' #OK
grep -i 'フォル' #OK
grep -i 'フォルダ' #NG エラー発生
  • そう、英語(半角英数)なら調子良く動いていたコマンドも、日本語を入力したとたん使い物にならなくなってしまった...。
  • それにしても、「フォル」がOKで「フォルダ」はNG。ファイルによっては、英数字も日本語もすべてNGだったりする。
  • いったい、何が原因でこのような現象になるのか?徹底的に調べてみた。

現象

  • テキストエディット.appで、デスクトップに以下のようなファイル(folder.txt)を用意した。
applications folder
documents folder
アプリケーションフォルダ
書類フォルダ
  • また、Finderでファイル名を選択してコピー、それをテキストエディットにペーストしたファイル(services.txt)を用意した。
照会_西濃運輸カンガルー便).workflow
照会_〒EMS.workflow
照会_佐川急便.workflow
照会_JPエクスプレスペリカン便・日本通運).workflow
照会_〒翌朝10時郵便.workflow
照会_〒書留.workflow
照会_〒ゆうパック.workflow
照会_ヤマト運輸.workflow
ターミナル
$ cat ~/Desktop/folder.txt | sed s/folder/OOOOOO/g #OK

$ cat ~/Desktop/folder.txt | sed s/フォルダ/OOOOOO/g #OK

$ cat ~/Desktop/services.txt | sed s/workflow/OOOOOO/g #NG 異常出力
照会_ヤマト運輸.OOOOOOOOOO ン便・日本通運).OOOOOO

$ cat ~/Desktop/services.txt | sed s/照会/OOOOOO/g #NG 異常出力
OOOOOO_ヤマト運輸.workflowowow 便・日本通運).workflow


$ cat ~/Desktop/folder.txt | grep 'folder' #OK

$ cat ~/Desktop/folder.txt | grep 'フォルダ' #OK

$ cat ~/Desktop/services.txt | grep -i 'jp' #NG 異常出力
照会_ヤマト運輸.workflowowow 便・日本通運).workflow

$ cat ~/Desktop/services.txt | grep -i '西濃' #NG 異常出力
照会_ヤマト運輸.workflowowow 便・日本通運).workflow

$ cat ~/Desktop/services.txt | grep -i 'カンガルー' #NG 抽出なし
$ cat ~/Desktop/services.txt | grep -i 'カン' #NG 異常出力
照会_ヤマト運輸.workflowowow 便・日本通運).workflow
Automator&「シェルスクリプトを実行」アクション
cat ~/Desktop/folder.txt | sed s/folder/OOOOOO/g #OK
cat ~/Desktop/folder.txt | sed s/フォル/OOOOOO/g #OK
cat ~/Desktop/folder.txt | sed s/フォルダ/OOOOOO/g #NG 置き換えなし
cat ~/Desktop/services.txt | sed s/workflow/OOOOOO/g #OK
cat ~/Desktop/services.txt | sed s/照会/OOOOOO/g #OK

cat ~/Desktop/folder.txt | grep -i 'folder' #OK
cat ~/Desktop/folder.txt | grep -i 'フォル' #OK
cat ~/Desktop/folder.txt | grep -i 'フォルダ' #NG エラー発生
cat ~/Desktop/services.txt | grep -i 'jp' #NG 全部抽出
cat ~/Desktop/services.txt | grep -i '西濃' #NG 全部抽出
cat ~/Desktop/services.txt | grep -i 'カンガルー' #NG 全部抽出
AppleScript&do shell script


do shell script "cat ~/Desktop/folder.txt | sed s/folder/OOOOOO/g" --OK
do shell script "cat ~/Desktop/folder.txt | sed s/フォルダ/OOOOOO/g" --OK
do shell script "cat ~/Desktop/services.txt | sed s/workflow/OOOOOO/g" --OK
do shell script "cat ~/Desktop/services.txt | sed s/照会/OOOOOO/g" --OK

do shell script "cat ~/Desktop/folder.txt | grep -i 'folder'" --OK
do shell script "cat ~/Desktop/folder.txt | grep -i 'フォルダ'" --OK
do shell script "cat ~/Desktop/services.txt | grep -i 'jp'" --NG(全部抽出)
do shell script "cat ~/Desktop/services.txt | grep -i '西濃'" --NG(全部抽出)
do shell script "cat ~/Desktop/services.txt | grep -i 'カンガルー'" --NG(エラー発生)
do shell script "cat ~/Desktop/services.txt | grep -i 'カン'" --NG(全部抽出)

Automator&「AppleScriptを実行」アクション&do shell script

分析

データは揃った。以上の結果から、考えられる原因を想像してみた。

改行コード
  • folder.txt(LF)とservices.txt(CR)の違いは、改行コードの違いだった。(CotEditorで開いて気付いた)
    • LF:UNIXで利用される改行コード。
    • CR:Macで利用される改行コード。
    • ちなみにWindowsでは、CR・LFを行末に二つ記入している。
  • fileコマンドで確認すると、以下のように表示される。
$ file ~/Desktop/folder.txt
/Users/zari/Desktop/folder.txt: UTF-8 Unicode text

$ file ~/Desktop/services.txt
/Users/zari/Desktop/services.txt: UTF-8 Unicode text, with CR line terminators
  • ターミナルでservices.txtがことごとく異常出力されてしまうのは、改行されずに同一行で上書き表示されてしまう現象が発生しているようだ。
  • sedの置き換え自体は、うまく処理されていた。
    • 但し、ターミナル画面では表示が重なってしまうため、異常な出力に見えてしまっていた。
    • ファイルに出力して、CRを改行と見なしてくれるエディタで確認すると、問題なかった。
  • grepの抽出では、テキスト全体が一行と認識されてしまうため、一つでもマッチすれば、テキスト全体が抽出される結果になってしまったようだ。
    • おまけに、表示が重なってしまうため、異常な出力に見えてしまう。(sedと同様)


以下のページがたいへん参考になりました。感謝です!

テキストエンコーディング

改行コードがLFであるfolder.txtについては...

  • Automatorの「シェルスクリプトを実行」アクションでのみ、「フォル」がOKで、「フォルダ」がNGという面白い結果になった。
  • ターミナル、あるいはAppleScript&do shell script では正常に処理される。

おそらく、文字コードに由来する原因だと思うが、どうして上記のような現象になるのかは、分からなかった。

  • fileコマンドで調べた限り、同じUTF-8であるはずなのに...。


ところで、改行コードがCRであるservices.txtについて、CRに問題があることが分かっているので、LFに変換してやり直してみた。

  • ターミナル
$ cat ~/Desktop/services.txt | /opt/local/bin/nkf -w -Lu | grep -i 'カンガルー' #NG 抽出なし
$ cat ~/Desktop/services.txt | /opt/local/bin/nkf -w -Lu | grep -i 'カン' #OK
照会_西濃運輸(カンガルー便).workflow
照会_JPエクスプレス(ペリカン便・日本通運).workflow


do shell script "cat ~/Desktop/services.txt | /opt/local/bin/nkf -w -Lu | grep -i 'カンガルー'" --NG(エラー)
do shell script "cat ~/Desktop/services.txt | /opt/local/bin/nkf -w -Lu | grep -i 'カン'" --OK
--結果↓
--"照会_西濃運輸カンガルー便).workflow
--照会_JPエクスプレスペリカン便・日本通運).workflow"

cat ~/Desktop/services.txt | /opt/local/bin/nkf -w -Lu | grep -i 'カンガルー' #OK
cat ~/Desktop/services.txt | /opt/local/bin/nkf -w -Lu | grep -i 'カン' #OK


つまり、改行コードがCRであるservices.txtについては...

  • Automatorの「シェルスクリプトを実行」アクションでのみ、正常に処理される。
  • ターミナル、あるいはAppleScript&do shell script では、「カン」はOKで、「カンガルー」はNGというfolder.txtとは全く逆の結果になった。


さらに面白いことに...

  • 最終行に自分自身が手入力した「照会_西濃運輸カンガルー便).workflow2」を追記してみた。
  • すると、今まで抽出なしだったターミナルでの grep -i 'カンガルー' で、抽出されたしまったのである!
$ cat ~/Desktop/services.txt | /opt/local/bin/nkf -w -Lu | grep -i 'カンガルー'
照会_西濃運輸(カンガルー便).workflow2


同じ「カンガルー」のはずなのに、ファイル名のコピーと、手入力に一体、何の違いがあるというのだ?こうなったら最後の手段、HexEditorを使って、バイトコードの並びを比較してみることにした。

# (カンガルー便) の部分のバイトコード
ef bc 88 e3 82 ab e3 83 b3 e3 82 ab e3 82 99 e3 83 ab e3 83 bc e4 be bf ef bc 89 #ファイル名をコピーしたもの
ef bc 88 e3 82 ab e3 83 b3 e3 82 ac e3          83 ab e3 83 bc e4 be bf ef bc 89 #手入力したもの


なんと!ファイル名をコピーした方は3バイト分余分なコードが付加されていたのだ!

UTF-8-MAC

その答えは、ここにあった。

  • びんずめ堂(たいへん参考になりました。感謝です!)
  • 同じUTF-8なのに、以下の違いがあったのだ。
    • UTF-8-MAC: 濁点や半濁点が付いた文字を文字+濁点という2文字を合成したものとして扱う。
    • 普通のUTF-8: 濁点の付いた文字も一文字として扱う。
  • おそらく、ファイル名をコピーすると、UTF-8-MAC文字コードとして記録される。
  • 一方、自分自身で手入力した文字は、普通のUTF-8文字コードとして記録される。

上記のように考えれば、「フォルダ」と「フォル」、「カンガルー」と「カン」の奇妙な現象も説明できる。

コード修正

理由が分かれば、方針を決めて、修正するだけである。

  • 改行コードはLF(UNIX形式)に統一する。
cat '入力ファイル' | nkf -Lu
cat '入力ファイル' | iconv -f UTF-8-MAC -t UTF-8
# あるいは
cat '入力ファイル' | iconv -f UTF-8 -t UTF-8-MAC
  • Automatorの修正は簡単で、以前のコードに改行コードの変換とUTF-8-MACの変換をする「シェスルクリプトを実行」アクションを追加するだけで完了した。
/opt/local/bin/nkf -w -Lu|iconv -f UTF-8 -t UTF-8-MAC

  • ターミナルやAppleScript&do shell script なら、パイプを挟んでこんな感じ。
$ cat ~/Desktop/folder.txt | /opt/local/bin/nkf -w -Lu | iconv -f UTF-8-MAC -t UTF-8 | sed 's/\(フォルダ\)/【\1】/g'
applications folder
documents folder
アプリケーション【フォルダ】
書類【フォルダ】

$ cat ~/Desktop/folder.txt | /opt/local/bin/nkf -w -Lu | iconv -f UTF-8-MAC -t UTF-8 | grep -i 'フォルダ'
アプリケーションフォルダ
書類フォルダ

$ cat ~/Desktop/services.txt | /opt/local/bin/nkf -w -Lu | iconv -f UTF-8-MAC -t UTF-8 | sed 's/\(カンガルー\)/【\1】/g'
照会_西濃運輸(【カンガルー】便).workflow
照会_〒EMS.workflow
照会_佐川急便.workflow
照会_JPエクスプレス(ペリカン便・日本通運).workflow
照会_〒翌朝10時郵便.workflow
照会_〒書留.workflow
照会_〒ゆうパック.workflow
照会_ヤマト運輸.workflow
照会_西濃運輸(【カンガルー】便).workflow2

$ cat ~/Desktop/services.txt | /opt/local/bin/nkf -w -Lu | iconv -f UTF-8-MAC -t UTF-8 | grep -i 'カンガルー'
照会_西濃運輸(カンガルー便).workflow
照会_西濃運輸(カンガルー便).workflow2


以上で、sedgrepで濁点と改行をまともに扱えるようになった!


nkfコマンド

  • iconvはOSX標準でインストールされているコマンドだが、nkfは自分でインストール必要がある。
  • MacPortが利用できる環境なら、以下の一行でインストールは完了する。
$ sudo port install nkf
  • もし、MacPortがまだインストールされていなければ、MacPortをインストールするのが近道だと思う。
  • MacPortをインストールするには、Xcodeのインストールが必要。
  • Xcodeは、MacBook付属のDVD、あるいはSnow LeopardのDVDで、インストールできる。