apk --updateという引数はない

# はじめに

軽量なDockerイメージを作るときに重宝されるAlpine Linuxのパッケージマネージャであるapk、そのapkでパッケージをインストールするときにつかう `apk` コマンド、その引数に `--update` というものがあると思っている人はいないでしょうか。何を隠そう私がその一人でした。

今日なんかいいオプションないかなと何気なく `apk add --help` を見てたところ、あると思いこんでいた `--update` がないことに気付きました。

ちなみに下記が執筆時点で最新のAlpine Linux 3.9での `apk add --help` の実行結果です。

<details><summary>apk add --helpの出力結果 (クリックで開きます)</summary><div>

```console
# apk add --help
apk-tools 2.10.1, compiled for x86_64.

usage: apk add [-h|--help] [-p|--root DIR] [-X|--repository REPO] [-q|--quiet] [-v|--verbose] [-i|--interactive] [-V|--version] [-f|--force] [--force-binary-stdout]
           [--force-broken-world] [--force-non-repository] [--force-old-apk] [--force-overwrite] [--force-refresh] [-U|--update-cache] [--progress] [--progress-fd FD]
           [--no-progress] [--purge] [--allow-untrusted] [--wait TIME] [--keys-dir KEYSDIR] [--repositories-file REPOFILE] [--no-network] [--no-cache] [--cache-dir CACHEDIR]
           [--cache-max-age AGE] [--arch ARCH] [--print-arch] [-s|--simulate] [--clean-protected] [--overlay-from-stdin] [--no-scripts] [--no-commit-hooks]
           [--initramfs-diskless-boot] [--initdb] [-u|--upgrade] [-t|--virtual NAME] PACKAGE...

Description:
  Add PACKAGEs to 'world' and install (or upgrade) them, while ensuring that all dependencies are met

Global options:
  -h, --help              Show generic help or applet specific help
  -p, --root DIR          Install packages to DIR
  -X, --repository REPO   Use packages from REPO
  -q, --quiet             Print less information
  -v, --verbose           Print more information (can be doubled)
  -i, --interactive       Ask confirmation for certain operations
  -V, --version           Print program version and exit
  -f, --force             Enable selected --force-* (deprecated)
  --force-binary-stdout   Continue even if binary data is to be output
  --force-broken-world    Continue even if 'world' cannot be satisfied
  --force-non-repository  Continue even if packages may be lost on reboot
  --force-old-apk         Continue even if packages use unsupported features
  --force-overwrite       Overwrite files in other packages
  --force-refresh         Do not use cached files (local or from proxy)
  -U, --update-cache      Alias for --cache-max-age 1
  --progress              Show a progress bar
  --progress-fd FD        Write progress to fd
  --no-progress           Disable progress bar even for TTYs
  --purge                 Delete also modified configuration files (pkg removal) and uninstalled packages from cache (cache clean)
  --allow-untrusted       Install packages with untrusted signature or no signature
  --wait TIME             Wait for TIME seconds to get an exclusive repository lock before failing
  --keys-dir KEYSDIR      Override directory of trusted keys
  --repositories-file REPOFILE Override repositories file
  --no-network            Do not use network (cache is still used)
  --no-cache              Do not use any local cache path
  --cache-dir CACHEDIR    Override cache directory
  --cache-max-age AGE     Maximum AGE (in minutes) for index in cache before refresh
  --arch ARCH             Use architecture with --root
  --print-arch            Print default arch and exit

Commit options:
  -s, --simulate          Show what would be done without actually doing it
  --clean-protected       Do not create .apk-new files in configuration dirs
  --overlay-from-stdin    Read list of overlay files from stdin
  --no-scripts            Do not execute any scripts
  --no-commit-hooks       Skip pre/post hook scripts (but not other scripts)
  --initramfs-diskless-boot Enables options for diskless initramfs boot (e.g. skip hooks)

Add options:
  --initdb                Initialize database
  -u, --upgrade           Prefer to upgrade package
  -t, --virtual NAME      Instead of adding all the packages to 'world', create a new virtual package with the listed dependencies and add that to 'world'; the actions of the
                          command are easily reverted by deleting the virtual package

This apk has coffee making abilities.
```

</div></details>

長いので `update`でgrepしますと、

```console
# apk add --help | grep update
           [--force-refresh] [-U|--update-cache]
  -U, --update-cache      Alias for
```

そう、 `--update` ではなく `--update-cache` が正しい引数でした。

とはいえ "apk add --update" でぐぐるといくつも記事が出てくるので「これはいったいぜんたいどういうことなのだろう？」と思って調べてみました。

## 間違った引数は無視されているだけなのでは？

と思って存在しない引数を使ってみたんですが、

```console
 # apk add --doesnotexists
apk: unrecognized option: doesnotexists
```

とちゃんと警告されます。
つまり `--update` はヘルプに載ってないにもかかわらず正しい引数として認識されているというわけです。

## ソースコードを読む

apkのコードは[gitリポジトリで公開されています](https://git.alpinelinux.org/cgit/apk-tools/)。

このソースを読んでみたところ、[getopt_long](https://github.com/alpinelinux/apk-tools/blob/v2.10.3/src/apk.c#L574)という関数で引数を処理していることがわかります。

この `getopt_long (3)` という関数は [man](https://linux.die.net/man/3/getopt_long) をみると

```
Long option names may be abbreviated if the abbreviation is unique or
is an exact match for some defined option.

(意訳)
--XXXX形式の長いオプションは他のオプションと重複しない限りであれば短縮可能である。
(完全に一致したオプションがあれば他のオプションの一部があっても一致したほうが選択される)
```

つまり、 `apk --update` の `--update` 引数は、 `--update-cache` の省略形として`getopt_long` が認識していたから使用できるわけです。

これがどういうことかというと、apkが新たに `--update-hogehoge` という引数を追加したら `--update` はエラーになりますし、そもそもこの挙動自体 `apk` の仕様というよりは `getopt_long` の仕様で短縮された引数でも使えているだけなので引数の解析を別のライブラリに変更しただけで使えなくなるかもしれません。~~まあさすがにないだろうけど。~~

## 昔は `--update` 引数があったんじゃないの？

と思って `--update-cache` より前に `--update` がないかをGitリポジトリ内で調べてみました。

すると `--update-cache` が追加されたのが2009年で、その時点で `--update` はありませんでした。

[--update-cacheが追加されたコミット](https://github.com/alpinelinux/apk-tools/commit/080fa825ddf932cf95ef38ffa2107e3a99d49610) (はじめて `git bisect` つかった)

てことで、やっぱり `apk` に `--update` なんて引数はありませんでした。

となるとなんで `apk add --update` という記述がインターネット上にいくつかあるのかが謎なんですが、最初に誰かが間違えたのが出回ってるんですかね・・・？

(追記) Docker hubの公式AlpineイメージのGitHubリポジトリの[ドキュメント](http://gliderlabs.viewdocs.io/docker-alpine/) に `apk add --update nginx` という記述があることがわかりました。これが元凶なのかはわからないのですが、とにかくtypo修正のPull Requestを投げてみました。

https://github.com/gliderlabs/docker-alpine/pull/503

# まとめ

- `apk --update` という引数は `apk --update-cache` の間違いです。
- `getopt` のGNU拡張 `getopt_long` はオプションが正確じゃなくても他に候補がないときには使えるため、`--update` でも `--update-cache` を指定したときと同じ動作になります。
- インターネットの記事をコピペするだけじゃなくてちゃんと自分で調べましょう。

以上、自分への自戒でした。気をつけます。