ファイルの最後の文字が改行なら 0 で終了、それ以外なら 1 で終了する Bash スクリプト。
かなりちまちましているけど、ファイルの末尾については重要な問題だと思っている。
今回は Bash のスクリプトで書いてしまったが、できれば POSIX 原理主義的なやり方も知りたいと思う。
The Open Group Base Specifications Issue 7
The Open Group Base Specifications Issue 7 によると
A file that contains characters organized into one or more lines.
A sequence of zero or more non- <newline> characters plus a terminating <newline> character.
どうやらテキストファイルとは「1 つ以上の行」行は「0 個以上の改行以外の文字と末尾の改行」らしい。
このように末尾が改行で終わっているか判定し、正常なら 0
、それ以外なら 0 以外
で終了コードを返すスクリプトが欲しくなった。
もちろん、「この仕様に基づいた上でのテキストファイルとは」という話なだけなので、
私が「この世の全てのテキストファイルとはかくあるべし」と主張しているわけではない。
いろいろな OS に、それぞれの仕様があると思う。
書いた
#!/usr/bin/env bash
exec tail -c 1 | cmp -s - <(echo)
使い方
終了コードだけではコンソールからは判別つかないので、 echo "$?"
をつけて例示してみる。
調べたいファイルを標準入力にする。
$ ./endswith_linebreak < target_file ; echo "$?"
0
なお Bash 特有の機能である、コマンドを <()
でリダイレクトする機能を使っている。
ので、このスクリプトは POSIX 準拠というわけではない。無念。
これではダメだった
最初、もっとシンプルに次のようなスクリプトを考えていた。
test "$(tail -c 1 "$1")" = $'\n'
残念ながら、これでは意図通りの動作にならない。
コマンド置換は末尾の改行が潰されてしまうのだ。そのため常に終了コードは 1 になる。
これは普段
$ echo "$(pwd)/somefile"
と実行したとき
$ echo "$(pwd)/somefile"
/foo/bar
somefile
とならずに
$ echo "$(pwd)/somefile"
/foo/bar/somefile
となることからも理解できる。
今回はそこでいろいろと苦心したものの、結局思い浮かばなかった。
Bash スクリプトで割りきって書くことにした。
また、せっかくなのでファイルを引数で指定するのではなく標準入力を使うことにした。
もし標準入力ではなく引数でファイルを指定する形式なら
#!/usr/bin/env bash
exec cmp -s <(tail -c 1 "$1") <(echo)
と書けばいい。
2015-05-30 追記:
コメント欄にて tail -n 1 | wc -l
すると良いとのことを教えていただいたので更新。
#!/bin/sh
set -eu
PATH='/bin:/usr/bin'
LANG='C'
exec test "$(tail -n 1 "$1" | wc -l)" -eq 1