混在する改行コードをBSD版sedで変換する
環境
CR+LF, CR, LF を全て LF に統一したい
改行コード CR+LF, CR, LF が混在した邪悪なファイル file.txt がある。
$ echo -n 'あいうえお\r\nかきくけこ\rさしすせそ\n' > file.txt
# file.txt あいうえお<CR+LF> かきくけこ<CR> さしすせそ<LF>
これをmacOS標準のBSD版 sed コマンドで LF に統一したい。
こうする。
$ sed -e ':L' -e 'N' -e '$!bL' -e 's/\r\n/\n/g' -e 's/\r/\n/g' file.txt あいうえお かきくけこ さしすせそ
file.txt 全体を読み込んでから CR+LF を LF に置換し、最後に残った CR を LF に置換する。ファイル全体を読み込むので、ファイルサイズが大きい場合は注意。
仕組み
処理の流れは下記のとおり。
:L: ラベルLを定義する。N: ファイルから次の行を読み込み、パターンスペース(sedが読み込んだ行を保存する領域)に追加する。$!bL: ファイル最終行$でない!場合は分岐(ジャンプ)bする。分岐先はラベルL。*1s/\r\n/\n/g: パターンスペース内で\r\nを\nに置換する。s/\r/\n/g: パターンスペース内で\rを\nに置換する。
1.〜3.のループにより、ファイル全体がパターンスペースに保存される。
4.〜5.では、パターンスペースに保存されたファイル全体の文字列に対して、それぞれ CR+LF→LF, CR→LF を行っている。
後述のとおり、通常sedは1行ずつの読み込み時に改行コード LF が含まれないため、CR+LFの行が /\r\n/ にはマッチしない。
しかし2.のようにNコマンドで読み込むことで、改行コード LF も含めてパターンスペースに保存されるため /\r\n/ で CR+LF の行にマッチするようになり、CR+LF → LF の置換ができる。
※後から気づいたんだけどこっちのが楽
$ sed -e 's/\r$//g' -e 's/\r/\n/g' file.txt あいうえお かきくけこ さしすせそ
CR+行末(つまり CR+LF での改行)の CR を削除してから、残りの CR を LF に置換する。
後述のとおり、sedの出力時にはもともと行末についていた LF も出てくるので、行末の CR を削除してしまえば自動的に LF での改行として出力される。
あとは CR で改行されているものだけ LF に置換すればよい。
うまくいかない例
単純に CR を削除する
$ sed -e 's/\r//g' file.txt あいうえお かきくけこさしすせそ
よくあるやつ。CR+LF を LF に変換するだけであればOKだが、CR のみの場合は改行コードが消えてしまうので今回の条件ではNG。
行ごとに CR+LF でマッチさせようとする
$ sed -e 's/\r\n/\n/g' -e 's/\r/\n/g' file.txt あいうえお かきくけこ さしすせそ
通常 sed は1行ごとに文字列を処理していくが、そのときの処理対象の文字列には改行コード LF は含まれないため、's/\r\n/\n/g' では CR+LF にマッチしない。結局 's/\r/\n/g' しか処理されないので、CR+LF は LF が二重になってしまう。
(例えば あいうえお\r\n という文字列は、sed コマンドで普通に読み込まれると あいうえお\r に見えており、\n が含まれない)
行ごとに CR+行末 でマッチさせようとする
$ sed -e 's/\r$/\n/g' -e 's/\r/\n/g' file.txt あいうえお かきくけこ さしすせそ
\r$ で CR+LF の行にマッチするものの、出力時には置換後の \n に加えて、もともと行末についていた \n も出てくるため、改行コードが二重になってしまう。
*1:つまりファイル最終行に到達するまで、1.に戻って、2.で次の行を読み込んで、…を繰り返す