macOSのターミナル.appで新規のウィンドウ/タブを開いた時に表示される最終ログイン日時の書式は変更できない
Q: なんのこと?
A: この赤枠のメッセージ日時の部分こと。
Last login: Tue Sep 12 14:07:59 on ttys001
と表示されたターミナルの画面
このメッセージを例えば、
Last login: 2023/09/12
Last login: 2023-09-12 14:07:59 on ttys001
といった具合に設定ファイルなどを使って変更できないということ。
Q: なんで変更できないの?
A: login(1) のコードに組み込まれているから。
やや長い機序の説明
macOSのターミナル.appは新規のセッションを開始する時(つまり、新しいウィンドウまたはタブを開いた時)、login(1) に適当な引数を渡してシェルを起動するとみられている。タイトルにて示した場面では、シェル(特にUnix shell)がlogin(1) の引数として渡され、ログインシェルとして起動される。
シェルを実行しているターミナル.appのインスペクタ
login -pf <user> /bin/sh
大事なのは、ターミナル.appがシェル(ここでは/bin/sh
)を直接起動するのではなく、login(1) がシェルを起動させているということ。言い換えれば、login(1) のプロセスにシェルのプロセスが子プロセスとして属するということになる。
ここで、login(1) の実装を見てみる(枝はmacOS 13.5を指しているが、やっていることはどれも大筋で変わりはないはず)。
リンク先の、dolastlog
関数の中で、
(void)printf("Last login: %.*s ",
24-5, (char *)ctime(&lastlog.ll_tv.tv_sec));
となっているのが該当の処理だろう。日付の書式は (char *)ctime
の部分で決め打ちされてしまっている感じであり、実行時に外部から変更できる余地は無さそうに見える。
このdolastlog
関数はmain
関数の中にふつう必ず呼び出される位置にあるため、ターミナル.appがシェルを起動するたびに画面にメッセージが表示されるし、日時の書式も変更できないというわけになる。
類似のコードで実験
#include <stdio.h>
#include <time.h>
int main(int argc, char const *argv[])
{
time_t now = time(NULL);
(void)printf("Last login: %.*s\n", 24 - 5, (char *)ctime(&now));
return 0;
}
$ make ctime
cc ctime.c -o ctime
$ LC_ALL="C" ./ctime
Last login: Wed Sep 13 17:01:26
$ LC_ALL="ja_JP.UTF-8" ./ctime
Last login: Wed Sep 13 17:06:06
$ LC_ALL="zh_TW.UTF-8" ./ctime
Last login: Wed Sep 13 17:13:00
ローケールが何であろうと日時の書式に変わりはない。
ChatGPTによる解説
提供されたC言語のコードを確認しました。これはシンプルなプログラムで、UNIXタイムスタンプを使用して"Last login"メッセージを表示するものです。プログラムの構造と基本的な要素について説明します。
#include
ディレクティブ:
#include <stdio.h>
: 標準入出力関連のヘッダーファイルをインクルードしています。#include <time.h>
: 時刻関連のヘッダーファイルをインクルードしています。
int main(int argc, char const *argv[])
:
- このプログラムは
main
関数から始まります。Cプログラムのエントリーポイントです。argc
とargv
はプログラムに渡されたコマンドライン引数の数と値を格納するための引数です。このプログラムでは使用されていません。
time_t now = time(NULL);
:
time_t
型の変数now
を宣言し、現在のUNIXタイムスタンプを取得して格納しています。time(NULL)
は現在の時間を表す数値を返します。
(void)printf("Last login: %.*s ", 24 - 5, (char *)ctime(&now));
:
printf
関数を使用してフォーマット付きのテキストを出力します。"Last login: %.*s "
: フォーマット文字列で、%.*s
は可変長文字列を受け取るための指定子です。%.*s
の前の24 - 5
部分は文字列の幅を指定しており、この場合、19文字の幅を持つ文字列が表示されます。(char *)ctime(&now)
:ctime
関数はUNIXタイムスタンプを人間が読みやすい日付と時刻に変換し、文字列として返します。この文字列が%.*s
に挿入されます。
return 0;
:
main
関数からの正常な終了を示すために0を返します。このプログラムは、ユーザーの最後のログイン日時を表示する簡単な例です。C言語の基本的な要素、フォーマット指定子、関数の呼び出し、標準ライブラリの使用など、プログラミングの基本的な概念を示しています。プログラムの実行時には、現在の日付と時刻に合わせた "Last login" メッセージが表示されます。
マニュアル
login(1) のマニュアルにも、ユーザーの最終ログイン日時を表示する旨の記述があることがわかる。
Immediately after logging a user in, login displays the system copyright notice, the date and time the user last logged in, the message of the day as well as other information.
Q: なんとかいじれないの?
A: 限定的にできる。
先ほどのマニュアルによれば、これに続けて
If the file .hushlogin exists in the user’s home directory, all of these messages are suppressed.
とある通り、.hushlogin というファイルをユーザーのホームディレクトリに配置することでメッセージ自体を隠すことができる。これはStack Overflowほか多くの質問サイトやブログなどで示されている方法である。
また、login(1) は /etc/motd を関連ファイルとして挙げている(motd とは message-of-the-day の略語)。motd(5) によれば、
NAME
motd -- file containing message(s) of the day
DESCRIPTION
The file /etc/motd is normally displayed by login(1) after a user has logged in but before the shell is run. It is generally used for important system-wide announcements.
Individual users may suppress the display of this file by creating a file named “.hushlogin” in their home directories.
とあるように、/etc/motd ファイルの内容をログイン後に表示できる。これもまたいくつかのサイトでみられる解説。ただしその内容は平文で書かれている通りであるから、冒頭で紹介した "Last login: ..." のように、その日その時のメッセージを逐次生成……という風にはいかない。
ソースコードがXcodeのプロジェクトとしてそっくり公開されているので、該当箇所を自分好みに修正して自力でビルドし直して再インストールする手はありそう。
Q: (タイトルで示された以外の)XXXの環境でもそのメッセージ出るよ
A: login(1) を経由して起動し、かつその実装が似た感じであればある
もちろんGUIなしの環境ではlogin(1) から入るのでふつうに出る
Q: ログインシェルとして起動してるのにそのメッセージでないよ
A: ログインシェルは必要条件でもないし十分条件でもない。
Q: Unixシェル以外ではどうなるの?
A: メッセージは表示されない。
ところでUnixシェル以外のシェルというと、さまざまなプログラミング言語の対話環境とかREPLがこれに当たる。実例として、irb (Interactive Ruby Shell) でやってみよう。irb とはマニュアルによれば、「irb is the REPL(read-eval-print loop) environment for Ruby programs.」とのこと。
ターミナル.appで新しいセッションでシェルを起動するには、[メニュー]>[シェル]>[新規コマンド…]から irb の絶対パスを入力して[実行]ボタンを押下する。こうすることで、適当なコマンドをターミナル.appがlogin(1) を介して起動するよう指示したことになる。言い換えれば、login(1) プロセスに適当なコマンドの子プロセスが直に属する格好になる。冒頭の画像もこののやり方で検証している。
macOS 10.14 Mojaveでは /usr/bin/irb をここに指定してやれば良い。
irbを新しいセッションで起動する
そうすると、新しいウィンドウが開き、irb のプロンプトがあらわれる。最終ログインに関するメッセージは見当たらない。
irbを起動したところ
インスペクタを確認してみると、
irbを起動した時のインスペクタ
login -pfq <user> /usr/bin/irb
こうあるように、-q フラグが追加されているのがわかる。このフラグは、「This forces quiet logins, as if a .hushlogin is present.」