はじめに
tail -r
と tac
はファイルを行単位で逆順に出力するコマンドです。ログファイルを逆順(新しい順)に出力したい時などに使います。
POSIX (Issue 8) で tail -r は標準化されます
ファイルを行単位で逆順に出力するコマンドは POSIX で標準化されていませんでしたが POSIX Issue 8(2022 年後期予定)で標準化される予定です。理由は、UNIXでは 1980 年以降 tail -r
をサポートしており POSIX はその慣習に従うべきだというものです。
0000877: tail should support the -r option (2014-09-18 提案、2014-10-30 受け入れ、2020-04-03 反映)
UNIX systems include support for tail -r since at least 1980.
The POSIX standard should follow existing practice.
調べてみると確かに 1979 年リリースの Version 7 Unix のソースコード に -r
オプションがあるようです。また 1980 年の4BSD のソースコード にも -r
オプションがあります。POSIX.2 が 1992 年なので、むしろなんで標準化されてなかったのか?とさ思えてしまいますね。
GNU tail では -r 未サポート
意外なことに、GNU tail では -r
オプションは coreutils v9.0 (2021-09-24) の時点で未サポートです(なんとなく GNU はなんでもサポートしてそうに思ってました)。代わりに GNU には tac
(cat
のつづりの反対)コマンドが実装されています。こちらに書いてあるように、ファイルの終りの部分を(元の順番で)出力する tail
と、順番を逆順に並び替える tac
とでは仕事の内容が全く異なるというのが -r
オプションを実装していない理由なのでしょう。理由としては合理的です。
tail -r
の実装に関しては「bug#18808: implement 'tail -r' as synonym for 'tac'」 (2014-10-23) で議論されています。特に POSIX を策定している Austin Group からの電話の話 が興味深く、tac
ではなく tail -r
を選んだ理由がすでに多くの実装がそれを持っているからということでした。また(誰もが思っていることだと思いますが UNIX とは違い) GNU は適応速度が早いと考えられるため、すぐに対応することができるか?という確認の連絡だったようです。このメーリングリスト流れを見ると、とっくに tail -r
を実装していてもおかしく無さそうに感じるのですが、少なくとも現時点では実装されていないようです。
GNU tail 用ポリフィル
さて、GNU tail が現時点で -r
オプションをサポートしてないので、今すぐ実装されたとしても普及するのは当分後でしょう。それまで最新の POSIX の機能が使えないというのは嫌ですね。ということで、今すぐ GNU tail で -r
オプションが使えるように GNU tac を使ったポリフィルを実装してみました。
tail() (
unset f r c n
OPTIND=1 # すでに getopts を使っている可能性があるので状態をリセットする必要がある
while getopts frc:n: OPT; do
case $OPT in # f, c オプションは使わないので無視しても良い
f | r) eval "$OPT=1" ;;
c | n) eval "$OPT=\$OPTARG" ;;
\?) exit 1
esac
done
[ ${r+x} ] || exec tail "$@"
shift $((OPTIND - 1))
[ ${n+x} ] || exec tac ${1+--} "$@"
tac ${1+--} "$@" | head -n "$n"
)
tail -rn2 -- /etc/hosts
対応しているのは POSIX で規定されているオプションのみです。また -r
と組み合わせられるのは -n
のみです。これは POSIX Issue 8 での要求に従ったものなので問題ないでしょう。ただし組み合わせても単に無視されエラーにはなりません。十分なテストは行ってないので、サンプルコードとして考えてください。
ただ実際の所 tac
の方が短く入力しやすいので特にターミナル上では tac
コマンドを実装してない環境で alias tac="tail -r"
や tac() { tail -r "$@"; }
とした方が良い気がします。上記のポリフィルは、どうしても POSIX に準拠したコードにしたい人向けですね。
商用 UNIX での注意点
Solaris 11 は対応しているようなので問題ありません(参考 Solaris - tail)。HP-UX 11i v3 はドキュメントを見る限り非対応のようです。AIX 7.2 は一見対応しているようなのですが、最大 20,480 バイトの制限があるようです(参考 AIX - tail)。
実は GNU tail のドキュメントに気になることが書いてあり、
BSD tail (which is the one with -r) can only reverse files that are at most as large as its buffer, which is typically 32 KiB.
BSD tail は 32 KiB までしか反転できないとのこと。FreeBSD や macOS で確認した所そんなことは無さそうなんですが?それにマニュアルにはそのような制限は書いて無さそうですし検索しても GNU tail のページ以外見つかりません。昔の制限かもしれませんが、AIX には似たような制限があるようです。
理由はなんとなくわかりますね。行単位で逆に並べるには、単純に実装するとファイルサイズが大きい場合に大量のメモリを消費するでしょう。パフォーマンスを考慮しながら省メモリで実装しようとするとこれはなかなか難しい問題です。ファイル全体の完全な逆転が必要ないのであれば、ファイル末尾 32 KB だけを読み込んで反転した方が遥かに省メモリで速いです。
この問題を本気で解決しようとすると結構面倒なので省略します。「逆順出力 tac と tail -r」の記事(とその先の StackExchange の内容)などが参考になるのではないかと思います。省メモリにするなら「行番号をつける」→「sort -r
で行番号を逆順にソートする」→「head -n
で出力行数を制限する(必要な場合)」→「行番号を取り除く」というのが一番実装が簡単でしょうか。出力行数が指定されている場合は「tail -n
で出力行数を制限する」→「行番号をつける」→「sort -r
で行番号を逆順にソートする」→「行番号を取り除く」に切り替えた方がいいかもしれません。気が向いたら GNU tail 用ポリフィルを改造してコードを書くかもしれませんし書かないかもしれません。
補足 sort
コマンドはデータが大きい場合に一時ファイルを使ったマージソートでソートするため省メモリです。
さいごに
ということで、tail -r
は POSIX で標準化されますよという話でした。そう言えばついこの間は head -c
が POSIX (Issue 8) で標準化されるという記事を書きましたね。
このように POSIX は歩みは遅くとも止まってしまった標準規格ではありません。改訂作業は今も続いています。tac
や tail -r
は POSIX で標準化されていないと書いてある記事を見かけますが、それも数年後には過去の情報となります。我々も POSIX に合わせて情報と知識をアップデートして行かなければなりません。