find コマンドがめっちゃ便利だった話

株式会社オズビジョンのユッコ (@terra_yucco) です。
今日は業務内で同僚のシェアしてくれた find コマンドが感動的だったのでついつい記事にしてしまいました。

# `find` is so powerful!!
## TL;DR

```terminal
find [starting-point...] -newerXY reference 
```

## Premise

```terminal
vagrant@homestead:~$ find --version
find (GNU findutils) 4.7.0-git
Copyright (C) 2017 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by Eric B. Decker, James Youngman, and Kevin Dalley.
Features enabled: D_TYPE O_NOFOLLOW(enabled) LEAF_OPTIMISATION FTS(FTS_CWDFD) CBO(level=2)
```

## 今までは

`find` に `-newer` があるのは知っており、これを使って「より新しいファイル」「より古いファイル」などを探すことはよくやっていました。
ただし皆様ご存知のように `-newer` は man にもあるように[^1]、比較対象はあくまでファイルです。なので、このようにやっていました。

```terminal
touch -t 201905051213.30 /tmp/baseline
find /path -type f -newer /tmp/baseline
rm -f /tmp/baseline
```

比較用の `/tmp/baseline` ファイルを時間指定で作成し、そのファイルとの比較で `find -newer` を使用し、終わってから削除するという流れです。

## `-newerXY` があれば

```terminal
find /path -type f -newermt "2019-05-05 12:13:30"
```

なんてシンプル！

### `-newerXY` の `X` と `Y`

せっかくなので、この神オプション (個人的) について man をベースに調べてみました。

>-newerXY reference
Succeeds if timestamp X of the file being considered is newer than timestamp Y of the file reference.

`X` のタイムスタンプが参照したファイルの `Y` のタイムスタンプより新しいと考えられる場合に成功する。

>The letters X and Y can be any of the following letters:

>- a   The access time of the file reference
- B   The birth time of the file reference
- c   The inode status change time of reference
- m   The modification time of the file reference
- t   reference is interpreted directly as a time

`X` と `Y` に使えるのは以下の文字

letter|意味
:--|:--
a|参照されたファイルのアクセス時間
B|参照されたファイルの生成時間
c|参照先の i-node 状態が変化した時間
m|参照されたファイルの更新時間
t|参照先を直接時間として解釈する

>Some combinations are invalid; for example, it is invalid for X to be t.  Some combinations are not implemented on  all  systems;
for example  B is not supported on all systems.  If an invalid or unsupported combination of XY is specified, a fatal error results.  Time specifications are interpreted as for the argument to the -d option of GNU date.  If you try to use the  birth  time  of  a  reference file, and the birth time cannot be determined, a fatal error message results.  If you specify a test which refers to the birth time of files being examined, this test will fail for any files where the birth time is unknown.

組み合わせによっては不正になる。例えば `X` に `t` は指定できない。
組み合わせによっては特定のシステムで動かないことがある。例えば `B` は全てのシステムでサポートされているわけではない。
もし不正、もしくはサポートされていない `XY` の組み合わせが指定された場合、致命的なエラーが発生する。
時間を指定した場合は GNU date に `-d` で渡した引数と同じように解釈される。
もし生成時間を参照しようとして、それが定義されていない場合は、致命的エラーのメッセージが出力される。ファイルの生成時間を検証しようとした場合、生成時間が不明なファイルに対してはその検証は失敗する。

### エンジニアとしては気になる不等号

`-newer` = より新しい なので、おそらく等しい場合は合致しないと予想しましたが、念のために検証しました。

まずファイル作成して

```terminal
$ vagrant@homestead:~$ touch -t 201905051213.30 /tmp/baseline
vagrant@homestead:~$ stat /tmp/baseline
  File: /tmp/baseline
  Size: 0               Blocks: 0          IO Block: 4096   regular empty file
Device: fd00h/64768d    Inode: 1449540     Links: 1
Access: (0664/-rw-rw-r--)  Uid: ( 1000/ vagrant)   Gid: ( 1000/ vagrant)
Access: 2019-05-05 12:13:30.000000000 +0000
Modify: 2019-05-05 12:13:30.000000000 +0000
Change: 2019-05-30 11:41:32.741698485 +0000
 Birth: -
```

同じ日時で検証。
合致しませんでした。

```terminal
vagrant@homestead:~$ find /tmp/ -newermt "2019-05-05 12:13:30"
vagrant@homestead:~$
```

次に 1s 前を指定。
合致しました！

```terminal
vagrant@homestead:~$ find /tmp/ -type f -newermt "2019-05-05 12:13:29"
/tmp/baseline
vagrant@homestead:~$ 
```

ちなみに、同じ日時を否定してあげると、きちんと合致しました。

```terminal
vagrant@homestead:~$ find /tmp/ -type f ! -newermt "2019-05-05 12:13:30"
/tmp/baseline
vagrant@homestead:~$ 
vagrant@homestead:~$ find /tmp/ -type f -not -newermt "2019-05-05 12:13:30"
/tmp/baseline
vagrant@homestead:~$ 
```

- `[referred files] -newer [base]` は `[referred files] > [base]`
- `[referred files] ! -newer [base]` は `[referred files] <= [base]`

### Birth の存在しないファイル

検証に touch で作成したファイルが stat で参照したら `Birth: -` となっていますので、せっかくなので man 記載の内容を試してみました。

```terminal
vagrant@homestead:/tmp$ find /tmp/ -type f -newerBt "2019-05-05 12:13:30"
find: This system does not provide a way to find the birth time of a file.
find: invalid predicate `-newerBt'
vagrant@homestead:/tmp$ echo $?
1
```

こんな感じになりました。話のネタにしかなりませんが。
※ちなみにここで立ち上げているのは Homestead の Vagrant です。

## Conclusion

今日のまとめとしては `find` コマンドめっちゃ便利という点です。
前にいた現場は HP-UX だったので、そもそも GNU の恩恵を受けられず、今の現場に来て最初に感動したのは `date -d` だったのも覚えています。

膨大なオプションのうち `-newerXY` しか紹介できなかったので、折に触れて他もまた触れ、使いこなしていきたいと思います。

[^1]: `-newer file`: File was modified more recently than `file`.  If file is a symbolic link and the `-H` option or the `-L` option is in effect, the  modification time of the file it points to is always used.<br>`-newer file`: ファイルが `file` より最近に変更されたかを示す。ファイルがシンボリックリンクで `-H` オプションもしくは `-L` オプションが効果的な場合は、リンク先のファイルの更新日時が参照される。