あなたの知らないlocateの世界

# locate
ファイル名データベースからパターンに合うファイルパスを表示します。

```shell-session
$ whatis locate
locate (1)           - ファイル名データベースからパターンに合うものを表示する
```

```shell-session
$ locate --version
mlocate 0.26
$ Copyright (C) 2007 Red Hat, Inc. All rights reserved.
このソフトウェアは GPL v.2 に基づいて提供されています。
このプログラムは法律が許す範囲で無保証で提供されます。
```

## locateのインストール
大抵のLinux ディストロにはデフォルトで入っています。
Archlinuxではmlocateパッケージをインストールすることで使えます。

```shell-session
$ sudo pacman -S mlocate
```

## ファイルパスを高速検索する

```shell-session
$ locate some_keyword
```

で`some_keyword`を含むパスを表示します。
私の環境では検索にかかる時間は、保存ハードウェアSSD上に29万件の登録があるデータベースで0.4秒くらい、保存ハードウェアHDD上に450万件の登録があるデータベースで13秒くらいです。

デフォルトでは`/var/lib/mlocate/mlocate.db`から検索します。
dbファイルの作り方は後述するupdatedbに記述します。


## locate オプション

```shell-session
$ locate --help
Usage: locate [OPTION]... [PATTERN]...
Search for entries in a mlocate database.

  -A, --all              only print entries that match all patterns
  -b, --basename         match only the base name of path names
  -c, --count            only print number of found entries
  -d, --database DBPATH  use DBPATH instead of default database (which is
                         /var/lib/mlocate/mlocate.db)
  -e, --existing         only print entries for currently existing files
  -L, --follow           follow trailing symbolic links when checking file
                         existence (default)
  -h, --help             print this help
  -i, --ignore-case      ignore case distinctions when matching patterns
  -l, --limit, -n LIMIT  limit output (or counting) to LIMIT entries
  -m, --mmap             ignored, for backward compatibility
  -P, --nofollow, -H     don't follow trailing symbolic links when checking file
                         existence
  -0, --null             separate entries with NUL on output
  -S, --statistics       don't search for entries, print statistics about each
                         used database
  -q, --quiet            report no error messages about reading databases
  -r, --regexp REGEXP    search for basic regexp REGEXP instead of patterns
      --regex            patterns are extended regexps
  -s, --stdio            ignored, for backward compatibility
  -V, --version          print version information
  -w, --wholename        match whole path name (default)
```

よく使うオプションを表に記載します。

|  オプション            | 役割                      |
|------------------------|----------------------|
|  -c, --count           | マッチしたファイルの数だけを表示する。  |
|  -d, --database DBPATH | デフォルトの/var/mlocate/mlocate.db以外のデータベースを検索する。  |
|  -e, --existing        | dbにあるけれども、実際のファイルシステム上に存在するファイルしか表示しない。  |
|  -i, --ignore-case     | 大文字小文字無視  |
|  -l, --limit, -n LIMIT | 表示数をLIMIT件に抑える。  |
|  -r, --regexp REGEXP   | 正規表現で検索する  |
|  --regex REGEXP        | 拡張正規表現で検索する  |
|  -S, --statistics      | ファイル数、ディレクトリ数などの統計情報を表示する  |



## 異なるデータベースファイルから検索する

```shell-session
$ locate -d /var/lib/mlocate/mymlocate.db
```

でデフォルトの`/var/lib/mlocate/mlocate.db`以外のデータベースファイルから検索します。


## 複数のデータベースファイルから検索する

```shell-session
$ locate -d /var/lib/mlocate/mymlocate1.db -d /var/lib/mlocate/mymlocate2.db key_word
```

のように-dオプションを複数使います。

または

```shell-session
$ locate -d /var/lib/mlocate/mymlocate1.db:/var/lib/mlocate/mymlocate2.db key_word
```

のように複数のデータベースファイルをコロンで区切ります。



## 複数のデータベースファイルから検索する(環境変数を使う)
コロンによって区切られた検索するデータベースのリストを環境変数`LOCATE_PATH`として指定しておくと、`-d`オプションをデフォルトで指定していることと同様に動作します。

```shell-session
$ export LOCATE_PATH='/var/lib/mlocate/mymlocate.db:/var/lib/mlocate/mlocate.db'
$ locate key_word
```

複数のデータベースファイルから検索するときは`LOCATE_PATH`にコロン区切りでdbファイルを列挙していきます。


### `LOCATE_PATH`をディレクトリ以下全てのファイルとして指定する

`LOCATE_PATH`を/var/lib/mlocate以下全てのファイルとするシェルスクリプトを紹介します。

以前まで`tr`と`sed`を使う方法で/var/lib/mlocate以下のdbファイル名を結合していましたが、`paste`コマンドを使うともっと簡単になります。
参考: [シェルスクリプトの中でjoin()とsplit()相当の事をやる](https://qiita.com/piroor/items/55ff672cb9f8e375e659)

```shell-session
$ export LOCATE_PATH=$(paste -sd: <(find /var/lib/mlocate -name '*.db'))
# OR
$ export LOCATE_PATH=$(find /var/lib/mlocate -name '*.db' | paste -sd: -)
```

`paste`コマンドのオプション`-s`で列結合から行結合に変更し、`-d :`でコロンで結合します。
上のスクリプトはpasteコマンドにリダイレクション`<`を使って`find`の結果を流し込み、下のスクリプトはパイプを使って`paste`の標準入力`-`に`find`の結果を流し込みます。
結果は同じなのでお好みで選んでください。


## マルチプロセスでlocate検索する
`locate`コマンドは高速に(数ミリ秒以内)にファイルを検索してくれるので速さに対しては大抵満足いっているのですが、SSDではなくHDDに保存された400万件超のファイルパスを検索するときは検索に10秒以上かかっているのでイライラが溜まってきます。
検索に10秒かかっても`locate`は古いコマンドなのでシングルスレッド動作です。いまどきシングルスレッドで動いているシステムは化石なので、スレッドを複数使って高速化を図りたいと自然に思うようになってきます。
大抵のLinuxディストロのデフォルトでマルチプロセスにコマンドを実行できる機能がついています。それが`xargs`コマンドです。
`locate`コマンドが検索するデータベースファイルが複数ある場合に限り、データベース一つ一つに対して`xargs`が`locate`コマンドの検索先を振り分けてくれます。

```shell-session
$ find /var/lib/mlocate -name '*.db' | xargs -P2 -I@ locate -d @ 'some_keyword'
```

上のシェルスクリプトと同様にして`find ...`で見つけ出したdbファイル全てに対して`xargs`が並列にコマンドを実行してくれます。
`xargs`のオプション`-P2`で2スレッドで並列処理を実行します。~~実行環境のCPUのコア数、スレッド数を超えない数値を指定しましょう。~~ -P0で自動的に出来る限り多くのプロセスを同時に実行しようとします。(`man xargs`を読んでください。)
`-I@`で@の位置に`find...`の結果、すなわちデータベースのパスを埋め込みます。
locateコマンドが'some_keyword'を含むパスをデータベースファイルから探して表示します。

```shell-session:シングル/マルチスレッド実行時間比較
$ # シングルスレッド
$ timeit locate -d $(find /var/lib/mlocate -name '*.db' | tr "\n" : | sed -e 's/:$//g') --regex 'usr.*\d+.*f'
0.35s user 0.02s system 97% cpu 0.378 total
0.35s user 0.01s system 99% cpu 0.363 total
0.37s user 0.00s system 99% cpu 0.375 total

$ # マルチスレッド
$ timeit find /var/lib/mlocate -name '*.db' | xargs -P2 -I@ locate -d @ --regex 'usr.*\d+.*f'
0.01s user 0.00s system 96% cpu 0.009 total
0.00s user 0.00s system 95% cpu 0.009 total
0.01s user 0.00s system 95% cpu 0.009 total
```


> timeitコマンドは実行時間を3回計測する自作shell functionです。

```zsh:timeit_function
#!/usr/bin/zsh
# shell scriptを3回実行し、実行時間を表示
# USAGE:
#   timeit [command] <args>
function timeit() {
    for i ($(seq 3)) time sh -c "$*" > /dev/null 2>&1
}
```


## 統計情報
検索データベースに登録したファイル数などを表示します。
-dオプションで統計情報を表示するデータベースを選択できます。
`LOCATE_PATH`に複数のデータベースがあればすべてのデータベースの情報を表示します。

```
$ locate -S
データベース /var/lib/mlocate/mlocate.db:
        95,256 辞書
        866,720 ファイル
        ファイル名に 91,967,343 バイト
        データベースに保存するのに 27,641,702 バイト使いました
```


## 正規表現で検索する

regexpは正規表現、regexオプションは拡張正規表現を使用して検索できます。

[ 正規表現と拡張正規表現の違い ](http://jmatsu.hatenablog.com/entry/2017/01/30/130952)

|  ERE    |      BRE       |               用途               |    代替表現  |
|---------|----------------|--------------------------------|---------------  |
|  ｜     |   unsupported  |   OR 表現                        |   I don’t know  |
|  +      |   unsupported  |   1文字以上の繰り返し            |   \{1,\}  |
|  ?      |   unsupported  |   0 or 1文字                     |   \{,1\}  |
|  ()     |   \(\)         |   グループ化                     |   –  |
|  {n,m}  |   \{n,m\}      |   n文字以上、m文字以下の繰り返し |   –  |


## AND検索

```shell-session
$ locate --regex 'document.*gz'
```

documentの後にgzが含まれるファイルを検索します。
> gzの後にdocumentが含まれるファイルは検索されないので、「documentかつgzが含まれるファイルを検索する」わけではありません。


## OR検索

```shell-session
$ locate --regex 'document|gz'
```

documentまたはgzが含まれるファイルを検索します。


## NOT検索

```shell-session
$ locate document | grep -v gz
```

`locate`オプションだけでできませんが、`grep -v`を併用することで、「documentは含まれるが、gzは含まれないファイル」の検索ができます。





# updatedb
`locate`で検索するデータベースファイル(.db)を作成、更新します。
`updatedb`により作成されるデータベースファイルは、ファイルパスのリスト情報が記されたバイナリファイルです。
大抵/var/lib/mlocate下に作成されるため、updatedbコマンドを使用するときはroot権限が必要です。
> この記事では`updatedb`をroot権限で実行するとして、sudoコマンドは省略して記載します。

* dbファイルを事前に作成しておかないと検索されません。
* dbファイルを事前に更新しておかないと古いファイルが検索されます。
  * 例えばdbファイルを作成した後に/home/hoge/attatchment.tar.gzを作成すると、存在するはずの/home/hoge/attatchment.tar.gzが検索結果に出てきません。
  * 例えばdbファイルを作成した後に/home/hoge/document.txtを削除すると、消されて存在しなくなったはずの/home/hoge/document.txtが検索結果に出てきます。


```shell-session
$ whatis updatedb
updatedb (1)         - ファイル名データベースを更新する
```


```shell-session
$ updatedb --version
updatedb (mlocate) 0.26
Copyright (C) 2007 Red Hat, Inc. All rights reserved.
このソフトウェアは GPL v.2 に基づいて提供されています。

このプログラムは法律が許す範囲で無保証で提供されます。
```

## updatedb オプション

```shell-session
$ updatedb --help
利用方法: updatedb [オプション]...
mlocate データベースを更新します。

  -f, --add-prunefs FS           同じ FS を除外する
  -n, --add-prunenames NAMES     同じ NAMES を除外する
  -e, --add-prunepaths PATHS     同じ PATHS を除外する
  -U, --database-root PATH       データベース中の保存するサブツリー (省略値 "/")
  -h, --help                     このヘルプを印刷する
  -o, --output FILE              更新するデータベース (省略値
                                 `/var/lib/mlocate/mlocate.db')
      --prune-bind-mounts FLAG   bind マウントを除外する (省略値 "no")
      --prunefs FS               データベースから除外するファイルシステム
      --prunenames NAMES         データベースから除外する辞書名
      --prunepaths PATHS         データベースから除外するパス
  -l, --require-visibility FLAG  ファイルを報告する前に可視性をチェックする
                                 (デフォルト値 "yes")
  -v, --verbose                  見つかったファイルのバスを印刷する
  -V, --version                  バージョン情報を印刷する

設定はファイル`/etc/updatedb.conf'
から読み込んだ値を省略値とします。

バグを https://pagure.io/mlocate へ報告してください。
```


## マウントしたドライブの検索データベースを作成する
updatedbは~~mntしたドライブを使ってデータベースを作成しない~~ので、 次のようにして別ドライブのパスをデータベースに含めます。
(Linux環境ではmntも含まれていましたが、Windosホストの仮想マシンでcronすると共有ドライブを/mntにマウントしたところがデータベースに含まれませんでした。システム管理者じゃないので詳しくはわからないのですがWindowsの共有ドライブ機能かレイドを組んでバックアップしていることが関係してそうです。)

```shell-session
$ updatedb -U /mnt/z --output /var/lib/mlocate/another.db
```

上のコマンドは/mnt/z以下のファイルを検索データベースを/var/lib/mlocate/another.dbに出力します。

更に`locate`検索するときは`-d`オプションを使ってanother.dbを指定するか、`LOCATE_PATH`にanother.dbを含めます。

`--output`を省略すると/var/lib/mlocate/mlocate.dbに出力されます。


# cron
定期的にコマンドを実行するデーモンを立ち上げます。


```shell-session
$ whatis cron
cron (8)             - 予定されたコマンドを実行するデーモン...
```

先述の通り、`locate`検索前に`updatedb`コマンドを打ってデータベースを最新の状態にしておかないと、存在するはずのパスが検索結果に出てこなかったり、過去に存在したファイルパスが検索結果に出てきてしまいます。
そのため、`updatedb`は定期的に実行しておく必要があり、大抵のLinuxディストリビューションでは`cron`により設定済みです。


# まとめ
* locateコマンドは高速ファイルパス検索コマンドです。
* locate検索するときにはupdatedbコマンドにより検索データベースを作る前準備が必要です。
* データベースコマンドは定期的に作成しておくべきで、cronデーモンを利用することが主流です。