LoginSignup
4
2

【find -mtimeに移植性はない】Unix標準コマンドを使ってる限り、どこでも動くシェルスクリプトなんて書けるわけねーだろ

Last updated at Posted at 2023-10-06

久しぶりに・・・キレちまったよ・・・

いつもキレてますが何か?

この記事の著者はイラツイてこの記事を書いています。
文章の内容は皮肉、愚痴、真実が含まれているので
そのまま受け取らないようにしてください。

find -mtime は POSIX で標準化されていますが互換性がありません。find -mtime nfind -mtime +nfind -mtime -n の範囲が環境によって違うのを知っていますか? find -mtime を使うシェルスクリプトは違う OS に持っていったら正しく動かないでしょう。

以下の検証は 0 または 1 を起点n(n日前)としてその前後のオフセット時間にマッチするか(<==> がマッチ範囲)を示したものです。流石に同じだと思うので n > 1 では検証していません。というか誰か他に興味がある人、再検証お願いします! これが本当に正しいのかさっぱりです。

         GNU                    macOS・Solaris 11         FreeBSD・NetBSD・OpenBSD

                          【find -mtime n の範囲(全部違う)】

            -mtime 0                -mtime 0                -mtime 0
------|--------|------  ------|--------|--------|------  ------|--------|------
    -24h <==> [0]           -24h <=== [0] ===> +24h           [0] <==> +48h(-1s)

            -mtime 1                -mtime 1                -mtime 1
------|--------|------  ------|--------|--------|------  ------|--------|------
    -24h <==> [1]           -24h <==> [1]                     [1] <==> +24h  


                         【find -mtime +n の範囲(右が違う)】

            -mtime +0               -mtime +0               -mtime +0
------|--------|------  ------|--------|--------|------  ------|--------|------
==> [-24h]     0        ==> [-24h]     0                 ====> 0

            -mtime +1               -mtime +1               -mtime +1
------|--------|------  ------|--------|--------|------  ------|--------|------
==> [-24h]     1        ==> [-24h]     1                 ====> 1


                    【find -mtime -n の範囲(全部違うが左右は似てる)】

            -mtime -0               -mtime -0               -mtime -0
------|--------|------  ------|--------|--------|------  ------|--------|------
               0 <====                 0     [+24h] <==       [0] <====+24h====

            -mtime -1               -mtime -1               -mtime -1
------|--------|------  ------|--------|--------|------  ------|--------|------
               1 <====                 1 <====+24h=====       [1] <====+24h====

[ ]は指定した値を含むの意味
なぜか n = 1 と n = 0 で挙動が違う。n = 0 の挙動は怪しい
n が増えると範囲は左にずれる(はず)
+48h(-1s) ・・・ どうも範囲が1秒小さい気がする

GNU 版は特に +n の指定が直感的ではない(+7 で 8 日以上前を意味する)のですが、指定範囲が重複していないという点で一貫性があります。また n の 0 を指定したときと 1 を指定したときの違いがありません。GNU 版には -daystart なんてのがありますが、macOS や BSD 版の動作に変更するものではありません。POSIX の規定はこの動作なのかよくわかりません。(日本語であっても)文章が難解な上に POSIX 自体でも混乱してますからね。
0001259: wrong description for find -atime +7 example

It's a classic mistake (caused by a confusing API).

macOS・Solaris 11 は 0 を指定したときは左右対称でキレイですが、1を指定したときはルールが違います。なんとなくマイナスの値の切り捨てが負の方向に行くのか正の方向に行くのかで混乱が起きてるような気がします。0 を指定することはあまりない、未来の日時のファイルは存在しないという前提だと GNU と同じだと言えそうです。Unix 認証を取っているのだから POSIX の仕様はこれなのかもしれませんが、0の場合と1の場合の違いは謎です。

FreeBSD・NetBSD・OpenBSD 版は n = 0 の指定でなんで +48h(-1s) まで範囲に含まれるのか謎です。+n と -n だけを見るとわかりやすい気がしなくもないですが、他と違いがあります。

AIX もなんか違うらしいです。デフォルトは POSIX に準拠してなくて環境変数を変えると POSIX に準拠するらしいです。ドキュメントからは n と n+1 の違いだけがあるように思えます。

https://www.ibm.com/docs/ja/aix/7.3?topic=f-find-command より

n
初期設定時間から減算されたファイル・アクセス時間を 86400 秒で割った値 (余りは切り捨てる) が n のとき、値は真となります。

-n
初期設定時間から減算されたファイル・アクセス時間を 86400 秒で割った値 (余りは切り捨てる) が n より小さいとき、値は真となります。

+n
初期化時間から減算されたファイル・アクセス時間を 86400 秒で割った値 (余りは切り捨てた値) が n ( UNIX03の場合、 n+ 1 より大きい値) より大きい場合は、真として評価されます。

注: -atime の定義は、Single UNIX Specification、バージョン 3 に準拠するように変更されています。 ファイルが 24 時間の n-1 から n の倍数でアクセスされた場合、 -atime の以前の動作は True と評価されます。 デフォルトでは、 find -atime は UNIX03より前と同様に機能します。 UNIX03 の動作は、環境変数 XPG_SUS_ENV を ON に設定し、 XPG_UNIX98 を OFFに設定することによって取得できます。

このオプションの以前の動作は、 XPG_UNIX98 変数を ONに設定することで取得できます。

-newer を使った回避策(バッドノウハウ)

-mtime のこの問題を回避する方法の一つに -newer を使った方法があります。-newer は特定のファイルとの日時を比較するものです。特定の日時のファイルは touch コマンドで作成することが出来ます。

$ touch -d '2023-10-06T00:00:00' /tmp/file.tmp
$ find /tmp -newer /tmp/file.tmp   # 新しいファイルを検索する場合
$ find /tmp ! -newer /tmp/file.tmp # 古いファイルを検索する場合

実際には決め打ちのファイルではなく一意な一時ファイルを作成して終わったら削除することになるでしょう。めんどくさい事この上ないですね。

ちなみにこの方法は「Unixパワーツール(鈍器)」の「9 章 findによるファイルの検索 - 9.8 ファイル時刻の精密な比較の方法」で紹介されている方法を少し修正したものです。同書では touch の日時の指定に読みづらい -t オプション (202310060000 という指定方法) が使われているので -d オプションに置き換えました。なお -t オプションも -d オプションも POSIX 準拠です。

あのさぁ

こんなん使って移植性があるシェルスクリプトなんてまともに書けるわけ無いだろ!
-newer 使った回避策は、アホらしいバッドノウハウでしかない。
このようなバッドノウハウをありがたがって使うのではなく根本から直すべき。

作りたいのは価値があるシェルスクリプト。互換性の違いに四苦八苦してシェルスクリプトで移植性を実現して見せましたなんて無駄な作業でしかない。そんなことをしてもバッドノウハウに詳しくなるだけで技術レベルは上がらない。

こんなバッドノウハウなんかなくしちまえ、
覚えたバッドノウハウなんか全部無駄でしたねって世の中にしちまえ。

Unixコマンドが酷いと他の言語の方がさくっと作れてしまう。Unix コマンドの酷さがシェルスクリプトの便利さをぶち壊している

現実の世界はPOSIX準拠の世界じゃない

POSIX は正しい。POSIX の仕様を守らないやつらが悪い!
POSIX準拠してないやつらは今すぐ仕様を変えて POSIX に準拠しろ!
過去のシェルスクリプトが動かなくなる?後方互換性なんかいらない!
移植性のほうが重要!POSIX準拠の方が重要!

どうせ BSD とか Unix なんかオワコンなんだから Linux に乗り換えるだろ?
全部 Linux の動きに合わせとけば良いんだよ。
そうすりゃ Linux にそのまま移植できる。

でもね、FreeBSD、NetBSD、OpenBSD は悪くないんですよ。
だって元から POSIX 準拠するなんて言ってないんですから。
Linux も POSIX 準拠するなんて言ってませんよね。

POSIXに準拠したいのは商用 Unix だけです。ただのブランドです。
POSIX 準拠って言っていれば昔はアメリカで政府機関が採用してくれたんです。
今じゃPOSIX 準拠にブランド的価値はありませんが。

つまり勝手に仕様を作った POSIX が悪い!
FreeBSD、NetBSD、OpenBSD は POSIX が作った変な仕様なんかに準拠しない!

作りたいのはどこでも動くシェルスクリプト、
それを移植性とか互換性とか考えずに簡単に作りたいのであって、
POSIX準拠とかどうでもいい。

Unix標準コマンドやPOSIXコマンドを使うなら移植性は諦めろ

結局ですね、Unix標準コマンドやPOSIXコマンドを使っても移植性はないんですよ。
POSIX コマンドに移植性がないのにPOSIXコマンドなら移植性があると思ってる人が多すぎる。
移植性を意識してると言いつつPOSIXを参照する所で終わって実際の動きを確かめない。
POSIX規格書に書いてあれば問題ないと思ってる。
POSIX規格書だけ眺めてても実際の動作なんてわからない。
実際の動作を見てみればこの通りバラバラ

Unixなんてもとからみんなバラバラ
移植性よりも互換性のほうが重要なのでPOSIXがなんて言おうが知ったこっちゃない
POSIXに準拠しても問題ない所は準拠してるけど、気に食わない部分はPOSIXに準拠しない

この問題を POSIX にどうにかしろって言っても解決しないんですよ。
POSIXは仕様を作るだけで、POSIXに準拠させる強制力を持ってませんから
そしてPOSIXに仕様をきっちり決めろと言っても決めないんですよ。
POSIXは「動作は『決まってない』が仕様だ」って書くだけで終わり
決まっていない動作に準拠させるのはシェルスクリプトを書く人の仕事

Unixに標準インストールされているコマンドに互換性はないし
そんなものを使ってるシェルスクリプトには当然移植性はありません。
せいぜい、運が良ければ動く、アホらしい苦労をして両対応すれば両方で動くというだけ

Unix標準コマンドなんて使うな、誰かが作ったPOSIX準拠コマンドをインストールしろ

Unix標準コマンドに互換性がないんだから、当然使えませんね。
もちろんPOSIXコマンドも互換性はありません。

じゃあどうする?POSIXコマンド以外のPOSIX準拠コマンドを使えばいい
POSIXとはC言語インターフェースなので、それさえ使ってれば
誰が作ったプログラムでもPOSIX準拠です。
POSIX準拠のプログラムを作るのにPOSIXコマンドなんていらんのですよ

C言語で作る必要もない。RustだってPythonだって最終的には
OSの機能を使う時にC言語インターフェースを利用しているわけで
POSIX準拠と言える。

そういうPOSIX準拠コマンドをインストールして使えばいい
UnixコマンドやPOSIXコマンドはね、いくつものベンダーが
同じコマンドを独自開発してるから互換性が下がってるんですよ。
開発ベンダーが一つなら互換性の問題は少ない。

移植性を求めるシェルスクリプトを書きたいなら
Unix標準コマンドやPOSIXコマンド使わなければいい
今どきインストールなんて簡単すぎる作業

さいごに ~ シェルスクリプトの未来を作るにはどうするか?

find コマンドを使わない。既存の代替コマンドがあればそれを使う。
find コマンドを使わないでいいように代わりのコマンドを作る。
古いログファイルの処理などは logrotete に任せる。シェルスクリプトでやらない。

残念ながら、find コマンドは Unix の標準コマンドで、OS 自身が使ってるので無くせません。しかし私達は Unix 標準コマンドを使う必要はありません。

Unix 標準コマンドには移植性がありません。ついでに低機能で使いづらくていいもんではありません。コマンドごとに開発者は異なり、昔の誰かが仕様を考えて、てんでバラバラに作ってるので統一性もありません。ここに昔の人が苦情を言って Dennis Ritchie が答えた find コマンドの設計の歴史があります。

要約すると「find コマンドは Dennis Ritchie たち研究チームが作ったものじゃなくて、商用 Unix チームが作ったもので変な構文だけど世間に広まった。」です。Unix コマンドは良いものではないんですよ。

POSIX はそのバラバラの Unix コマンドの仕様をまとめたものであって、POSIX が素晴らしい仕様を考えて普及させているわけではありません。Unix コマンドも POSIX コマンドも最初からキッチリ作られておらず、これからもキッチリした仕様にはならないのです。後方互換性の方が移植性よりも大事で仕様を変えたり決めたりしないからです。他の実装との互換性は低いままなので、Unix コマンドを使うシェルスクリプトの移植性は低いのです。

POSIX に記載されている内容ではほぼ確実に正しいと言えることがあります。それは「未指定」や「実装定義」や「未定義」と書かれている所です。それは移植性がないとわかっているからわざわざ書いているわけです。つまり POSIX は移植性があるものを調べるものではなく、移植性がないものを調べるものだということです。POSIX で標準化されていても実際に調べなければ移植性があるかはわかりませんが、移植性がないものに関してはほぼ確実にわかります。

Unix 標準コマンドに未来はありません。シェルスクリプトの未来を作るには Unix 標準コマンドを捨て去らなければいけません。捨て去ると言っても OS からは消えないので、私達が使わないようにするということです。

それは今までのシェルスクリプトとは全く違うものになるでしょう。しかし今のシェルスクリプトに問題があるわけですから、それまでのやり方を変える必要があります。その結果、今の Unix コマンドの知識は無駄なものになるでしょう。でも本質を理解している人であれば、新しいコマンドに簡単に乗り換えられます。Unix コマンドの使い方は普遍の知識ではありません。Unix の普遍の知識とはさまざまなコマンドを組み合わせるという考え方です。この考え方さえ身に着けていれば、Unixコマンドが非推奨になっても別のコマンドを使えばいいだけなので困ることはありません。Unix コマンドが非推奨になって困る人は、Unix の考え方を理解しておらず Unix コマンドの使い方だけしか知らない人です。

シェルスクリプトの未来に必要なものは変化です。古くて罠が多くて使いづらい Unix コマンドを使わなくて良い、今までのバッドノウハウが不要な世界に変える必要があります。

4
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2