混在する改行コードを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.で次の行を読み込んで、…を繰り返す