注文の多いタイトルである。
- テキストファイル中の連続する空白行を1行に縮めたい。
- 縮めた結果を元ファイルに上書きしたい。元ファイルのバックアップは残さなくてよい。
- CR、LF、CRLFのいずれの改行コードのファイルでも使えるようにしたい。
- 出力結果では元の改行コードを保持したい。
- 複数種類の改行コードが混在しているファイルは、さすがに想定外とする。
なぜ、こんなに注文が多いのか?
元々は、astyle(1)(Artistic Style)を弄くっていて:
astyle --style=kr \ --indent=tab=4 \ --indent-namespaces \ --indent-labels \ --indent-preproc-define \ --break-blocks \ --pad-oper \ --pad-header \ --unpad-paren \ --add-brackets \ --align-method-colon \ --unpad-method-prefix \ --pad-method-colon=none \ --max-code-length=120 \ --errors-to-stdout \ ${@+"$@"}
――こんなオプションでいい感じになるのだが、唯一気に入らないのが、2行以上の連続する空白行がそのまま残ってしまうこと。1行にしたいのだ。
astyle(1)には--delete-empty-lines
というオプションがあるが、これだと1行だけの空白行も消えてしまう上に、関数内でしか有効にならない。そうじゃなくて、1行だけの空白行はそのまま残しつつ、2行以上の連続する空白行を1行にしてほしい。関数の中と外のどちらでも適用されてほしい。
astyle(1)にかけるソースファイルには、改行がCRLFのものとLFのものが混在している。なので、どの改行コードのファイルでも処理できるようにしたい。出力結果の改行コードは元ファイルに準じてほしい。
astyle(1)は、元のファイルのバックアップを作成しつつ、元のファイルの名前で処理結果を出力する。その出力結果を上書きする感じで、空白行の処理を行いたい。大本のファイルはastyle(1)が残しているので、空白行の処理を行う前のファイルは残さなくてもよい。
ということで、まずはこんな感じのコードを(「これ、遅いだろうな」と思いつつ)書いてみた。
for i; do cat "$i" | (rm -f "$i"; sed '/./,/^$/!d' >"$i") done
しかし残念なことに、手元の環境(Cygwinのsed(1)を使用)では、CRLFが問答無用でLFになってしまった。どうもsed(1)が原因のようだ。
sed(1)を止めてPerlにすることにした。最初に書いたのがコレ。
perl -0777 -pi -e 's/(\r\n|\n|\r){3,}/\1\1/g' ${@+"$@"}
「-0777
」で入力ファイルを一気に読み込むことで、正規表現を用いて連続する改行コードにマッチできるようにしている。また、オプションiを拡張子無しで用いて、入力ファイルを出力結果で上書きするようにしている。
これでうまくいくかと思いきや、例えば次のようなファイル(CRLF)にて:
1 aaaaa 2 bbbbb
1行目の末尾から3行目の頭の間の「\r\n\r\n」が「\n\n」になってしまった。つまり、CRLFのファイルを食わせると、CRLFとLFが混在したファイルができあがる可能性がある。
これは正規表現の問題で、本来なら、CRLFのファイルでは「CRLFが3つ以上」に、LFのファイルでは「LFが3つ以上」に……という具合にマッチさせなくてはならないところを、無理やり1つの正規表現にしたために、「\r\n\r\n」が「『CRかLF』が4つ」と解釈されてマッチしてしまう可能性が生じていたのだ。
上記を踏まえて書き直したのがコレ:
perl -0777 -pi -e ' foreach my $nl ("\r\n", "\n", "\r") { s/(?:$nl){3,}/$nl$nl/g; } ' ${@+"$@"}
これで意図したとおりに動作するようになった。
ところで、大抵のファイルでは改行コードが混在している可能性は低い。なので、3種類の改行コード全てについてマッチングと置換をする必要もないだろう。
ということで、ファイル中の最初の改行コードを調べて、その改行コードでのみ置換を行うようにしてみた。
perl -0777 -pi -e ' foreach my $nl ("\r\n", "\n", "\r") { if (/$nl/) { s/(?:$nl){3,}/$nl$nl/g; last; } } ' ${@+"$@"}
これで多少は速くなった……かもしれない。