unexpand.awk の解説

なんでこんなものを作ったのか

きちんとインデント付けされたソースコードであれば、少々の微妙な点もありますが、expand(1) と unexpand(1) を適切に組み合わせて、任意のインデントスタイルに変更できます。理論的には。
ところが、スペース4個でインデント付けされたコードをタブに変換しようとして unexpand -t 4 としたところ、行頭だけではなく任意の位置の桁揃えをタブに変換する -a オプションを付けたような変換をされてしまっていることに先日気づきました。
私が使っているのは FreeBSD ですが man expand でマニュアルを読んでみてもそれらしきことは書いてありません。しかしマニュアルがあてにならないのはたまによくあることなのでソースを見ると、

case 't':	/* Specify tab list, implies -a. */
	getstops(optarg);
	all = 1;
	break;

( /usr/src/usr.bin/unexpand/unexpand.c )
のように -t オプションを付けると問答無用で暗黙のうちに -a と同じ効果がある、ということがわかります。
結局POSIXを確かめたところ、

When -t is specified, the presence or absence of the -a option shall be ignored; conversion shall not be limited to the processing of leading <blank> characters.

http://pubs.opengroup.org/onlinepubs/9699919799/utilities/unexpand.html

(理由に関する記述は特に無いようでした)とありましたので、これはバグなどではなく、少なくとも標準が要求している仕様ではある、というわけです。
いずれにしろ、これでは困るので、タブ幅4を指定して行頭のみタブに変換するunexpandが必要だ、というわけで実装しました。

実装

書いてある通りに読めると思いますので、特に説明が必要な点は無いと思います。
入力の行頭部分は全てスペースであることを前提としたいわけですが、タブ混じりの場合にも対応しています。対応方法は単純化のため、一旦全てexpandしてしまう、という方法をとっています。その際のタブストップは指定された幅(デフォルトは8)になります。もしこの挙動が困る(スペースへの展開とタブへの再解釈で幅を変えたい)場合は、通常の expand による前処理を通すだけでよいので、たいていは問題ないでしょう。
(細かいことを言うと、このスクリプトに内蔵のexpandは、行頭部分のみを展開するという点が expand(1) コマンドと異なる。GNU Coreutils 版の expand/unexpand には、以上のような行頭のみor全部に関係する動作を制御する非標準のオプションがある)