Ubuntu Weekly Recipe

第516回command-not-found再発見

Ubuntuにはcommand-not-foundというパッケージが最初からインストールされています。ユーザーが入力したコマンドが存在しない場合、必要となりそうなパッケージを推測・提案してくれるニクイアイツです。今回はその仕組みとカスタマイズ方法を紹介しましょう。

command-not-foundの仕組み

UbuntuでCLIで操作している時、およそ人生においてコマンド入力を間違えたことのないパーフェクトヒューマンでもない限り、以下のようなメッセージに遭遇したことがあるでしょう。

プログラム 'sl' はまだインストールされていません。 次のように入力することでインストールできます:
sudo apt install sl

これはcommand-not-foundというパッケージが提供する機能です。Ubuntuのリポジトリには多種多様なソフトウェアが存在しますが、そのすべてがローカルシステムにインストールされているわけではありません。特に初期のUbuntuは「インストールイメージをCD一枚のサイズに収める」という制約を設けていたため、最初からインストールされているパッケージは限られていました。しかしながら実行しようとしているコマンドとそのパッケージ名が即座にわかるユーザーはそこまで多くありません。そんなユーザーのために、入力したコマンドが見つからなかったときに、可能性のあるパッケージをリストアップしてくれるのがcommand-not-found機能です。

command-not-foundはUbuntu 6.10ぐらいにパッケージが用意され、Ubuntu 7.04から標準でインストールされるようになった、Ubuntuの中ではとても歴史の古いツールです[1]⁠。オリジナルの作者はCanonicalのZygmunt KrynickiMichael Vogtであり、その実体はPythonスクリプトとなっています[2]⁠。

仕組みはすごく単純です。Bashには検索パスにコマンドが見つからない場合、command_not_found_handle関数を呼び出すという仕組みが存在します。Ubuntuのbashパッケージが提供する/etc/bash.bashrcには最初から、command-not-foundがインストール済みならcommand_not_found_handleを定義するスクリプトが組み込まれていますので、ユーザーが特に設定せずともcommand-not-foundを使えるようになっているのです[3]⁠。

# if the command-not-found package is installed, use it
if [ -x /usr/lib/command-not-found -o -x /usr/share/command-not-found/command-not-found ]; then
        function command_not_found_handle {
                # check because c-n-f could've been removed in the meantime
                if [ -x /usr/lib/command-not-found ]; then
                   /usr/lib/command-not-found -- "$1"
                   return $?
                elif [ -x /usr/share/command-not-found/command-not-found ]; then
                   /usr/share/command-not-found/command-not-found -- "$1"
                   return $?
                else
                   printf "%s: command not found\n" "$1" >&2
                   return 127
                fi
        }
fi

command-not-foundの実体は/usr/lib/command-not-foundです。ここにコマンド引数の最初の文字列(つまりはユーザーが入力したコマンドだと思われる文字列)を渡します。そして次のような流れでメッセージを表示します。

  • PATHにはないもののインストール済みかをチェックする
  • apt/aptitudeがインストールされていない場合は、ただ「見つかりません」のエラーのみを表示する
  • ブラックリスト(後述)に見つかったら、ただ「見つかりません」のエラーのみを表示する
  • パス名から該当するパッケージを検索する
  • パッケージが複数見つかったらパッケージリストとインストール方法を表示する
  • 該当するパッケージが1つのときはインストール方法sudo apt PACKAGEを提示する
  • 該当パッケージのコンポーネント(mainやuniverse)が無効化されていたら有効化するように伝える
  • パッケージが見つからずなおかつコマンドが3文字以上なら単なるスペルミスの可能性を考慮して「もしかして」を表示する

つまり「間違え方」によって結構表示の仕方が変わるのです。ちなみにadminグループに加入していたらsudoを使ったインストール方法が提示されますが、加入していない場合は次のようなメッセージになります。

プログラム 'sl' はまだインストールされていません。 'sl' を利用するために、コンピュータの管理者に 'sl' をインストールすることを相談してください

動作をカスタマイズする

command-not-foundは初心者にとって便利な機能ですが、ユーザーによっては「うざったい」と感じるツールでもあります。そこでいくつかのカスタマイズ方法も紹介しましょう。

command-not-foundを無効化する

無効化する一番手っ取り早い方法は、command-not-foundパッケージを削除することです。もしシステム全体としては残しておいて、特定のユーザーだけ無効化したいのであれば~/.bashrcに以下のスクリプトを追加しましょう。

# disable command-not-found
function command_not_found_handle {
    printf "%s: command not found\n" "$1" >&2
    return 127
}

要するに関数の定義を上書きしているだけですね。

オプション付きで実行する

command-not-foundにはいくつかのオプションがあります。⁠無効化する」と同じような方法で~/.bashrc上で関数を上書きすることで、任意のオプションを指定できます。

使えるオプションは次のとおりです。

  • --data-dir⁠:データディレクトリを/usr/share/command-not-foundに変更します。
  • --ignore-installed⁠:インストール済みかどうかのチェックを行いません。
  • --no-failure-msg⁠:候補が見つからなかったときは何も表示しません。

データディレクトリはパッケージ名とコマンド名のデータベースを格納しているディレクトリです。データベースはcommand-not-found-dataパッケージが提供します。独自のデータベースを使いたい場合などに使用します。

「インストール済みかどうかのチェック」は少し説明が必要かもしれません。たとえば/sbin/partedがインストール済みの環境でなおかつ環境変数PATH/sbinが含まれていない場合を考えます。bash的にはpartedは見つからない状態になるのですが、command-not-foundは環境変数PATHだけでなく「一般的なコマンドパス」も検索しているため/sbin/partedコマンドを見つけられます。これが「インストールされているが見つからないコマンド」です。

command-not-foundはこのようなコマンドを「インストール済み」として扱いそのフルパスを提示します。しかしながら、PATHで見つからない場合は存在しないものとしてパッケージを提案してほしい場合もあるかもしれません。--ignore-installedはそんな用途に適したオプションです。

候補が1つだったらインストールするか問い合わせる

候補が1つだった場合、対象のパッケージをインストールしてくれると、表示されるメッセージをコピー&ペーストする手間が省けます。環境変数COMMAND_NOT_FOUND_INSTALL_PROMPTに適当な値をセットしておくと、そのような動作になります。

$ export COMMAND_NOT_FOUND_INSTALL_PROMPT=y
$ sl
プログラム 'sl' はまだインストールされていません。 次のように入力することでインストールできます:
sudo apt install sl
インストールしますか? (N/y)

何も入力せずにエンターを押した時はインストールしないようになっていますので安心ですね。

特定のコマンドはデータベース問い合わせさせない

「よく打ち間違えるコマンド」ってありませんか?

「grep」「grpe」「git」「gti」になっていたり、⁠ls」のつもりが「rm」って打っていたり、⁠vi」って打つつもりが「emacs」って打ってしまったり……。

いわゆる「手グセ」に起因するものから、キーボードが原因だったり、睡眠不足が原因だったりと打ち間違える理由は様々です。また頻繁に打ち間違えるものに対して、毎回「まだインストールされていません」とか言われるとイラッときますよね。いや、自業自得なんですが。

~/.command-not-found.blacklist「よく打ち間違えるコマンド」をいくつか入れておくと、command-not-foundはそれを無視してくれます。

$ sl
プログラム 'sl' はまだインストールされていません。 次のように入力することでインストールできます:
sudo apt install sl
$ echo "sl" >> ~/.command-not-found.blacklist
$ sl
sl: コマンドが見つかりません
$ echo "grpe" >> ~/.command-not-found.blacklist
$ grpe
grpe: コマンドが見つかりません

データベースへの問い合わせをしなくなるだけで、command-not-foundコマンドは起動するので少ししか応答性はあがりませんが[4]⁠、それでもそれなりに気持ちが楽になることでしょう。

Ubuntu 18.04 LTSでの変更点

このcommand-not-found、ここ数回のUbuntuリリースにおいてはデータベースファイルの更新しか行っていませんでした。つまりコードはほぼそのままでした。修正されたとしてもPython3への対応とか、apt-getからaptへの変更など、周囲の環境にあわせた微調整にとどまっていたのです。

しかしながらUbuntu 18.04 LTSの開発期間では、何がどうなったのか活発にコミットが行われます[5]⁠。1リリース1コミットぐらいの頻度だったのが、ここ半年で60コミット以上、当社比60倍です!

  • snapパッケージのコマンドのサポート
  • snapパッケージとDebianパッケージの両方の候補があればバージョンも表示
  • データベースをGNU dbmからSQLiteへ移行
  • リポジトリ上のコマンドメタデータからデータベースを構築(未実装?)
  • pyflakes/autopkgtestなどによるコードチェックやテストの実装
  • 数千文字渡したときにシステムが固まる問題の修正LP: #1605732
  • より読みやすくなるよう表示内容の調整
  • Python2のサポート終了

おそらくユーザー側が最も恩恵を受けるのが、snapの対応とSQLiteへの移行でしょう。特にSQLiteになることで、低スペック環境下でのcommand-not-foundの「待ち」がだいぶ減ったように感じます。

コマンドメタデータからのデータベース構築は、sudo apt update時をトリガーとしてリポジトリ上のデータからデータベースを再構築する仕組みです。ただしまだリポジトリ側が対応していないようにも見えますので、おそらく将来を見据えた実装なのでしょう。

このようにcommand-not-foundは18.04で大きく変わる予定です。これを機に、今一度手元のツールを見なおしてみてはいかがでしょうか。

おすすめ記事

記事・ニュース一覧