🧑‍💻

Zshで環境変数とその値を補完する

2021/07/26に公開

シェルからコマンドを実行する際、環境変数を都度指定して渡すことがあります。たとえば、Railsにおける RAILS_ENV や、Google Cloud SDKの gcloud コマンドを起動する際の CLOUDSDK_ACTIVE_CONFIG_NAME は代表的な例です。

% RAILS_ENV=production rails assets:precompile
% CLOUDSDK_ACTIVE_CONFIG_NAME=myproject-production gcloud app logs tail

こうした指定はよく行うにも関わらず、タイプ数が多く面倒ですね。シェルの履歴に頼れば省力化はできるものの、変数の値を変えて再実行、のような場合は値を手打ちすることになります。

さて、zshには高機能な補完定義機能がありますが、こうした環境変数名やその値の補完定義を書くにはどうすればいいでしょうか。

変数名を補完する

Zshでは、環境変数名を書けるところでは、定義済みの環境変数名が補完できます。

% PAT ← ここでTABを押す
% PATH=

ところが、 RAILS_ENVCLOUDSDK_ACTIVE_CONFIG_NAME などの変数はふつう定義されていないので、zshはその存在を知る由もなく、補完することができません。

これをzshに教え込むには、 zstyle コマンドを用いて fake-parameters に設定します。

zstyle ':completion:*' fake-parameters RAILS_ENV:string CLOUDSDK_ACTIVE_CONFIG_NAME:string

これで、 CLOUDSDK_ACTIVE_CONFIG_NAME という変数があるということがzshの補完システム(正確には _parameters 関数)に認知されるようになります。

% CLO ← ここでTABを押す
% CLOUDSDK_ACTIVE_CONFIG_NAME=

変数の値を補完する

さて、長ったらしい名前が補完できるようになって楽になりましたが、その値も補完したいですね。こんなところでミスタイプはしたくないところです。 CLOUDSDK_ACTIVE_CONFIG_NAME の場合は、 gcloud コマンドで一覧できるconfiguration名の中から選びたいでしょう。

% gcloud config configurations list
NAME                  IS_ACTIVE  ACCOUNT          PROJECT               COMPUTE_DEFAULT_ZONE  COMPUTE_DEFAULT_REGION
default               False
myproject-demo        False      knu@example.com  myproject-demo
myproject-production  False      knu@example.com  myproject-production
myproject-test        True       knu@example.com  myproject-test

補完候補の生成

補完定義を書く前に、候補の一覧を生成するコマンドを考えましょう。この出力形式なら、awkを使うのが手軽そうですね。

% gcloud config configurations list | awk 'NR>=2{print $1}'
default
myproject-demo
myproject-production
myproject-test

あるいは、まじめにJSONで処理するのもいいでしょう。

% gcloud --format=json config configurations list | jq -r '.[].name'
default
myproject-demo
myproject-production
myproject-test

補完定義の作成

Zshで変数の補完は -value-,変数名,… という特別なコンテキスト名で定義します。つまり、次のようなファイルを fpath のどこかに置けばOKです。

_cloud_active_config_name
#compdef -value-,CLOUDSDK_ACTIVE_CONFIG_NAME,-default-

compadd -- $(gcloud --format=json config configurations list | jq -r '.[].name')

新たに置いたものを読み込ませるには compinit を実行します。

% compinit

これで、補完が効くようになりました。

% CLOUDSDK_ACTIVE_CONFIG_NAME= ← ここでTABを押す
default  myproject-demo  myproject-production  myproject-test

全部 .zshrc の中で管理したいという場合は、自分で関数を定義して compdef に渡します。

_cloud_active_config_name() {
  emulate -L zsh
  compadd -- $(gcloud --format=json config configurations list | jq -r '.[].name')
}
compdef _cloud_active_config_name -value-,CLOUDSDK_ACTIVE_CONFIG_NAME,-default-

いろいろな例

ほかにも、アイデア次第で様々な変数の値を補完して便利に使うことができます。

変数名自体も補完できるよう、 fake-parameters に足すのも忘れないようにしましょう。複数指定する場合はブレース展開を使うと便利です。

zstyle ':completion:*' fake-parameters {RBENV_VERSION,RAILS_ENV,CLOUDSDK_ACTIVE_CONFIG_NAME,ASDF_{ELASTICSEARCH,JAVA,NODEJS,POSTGRES,POSTGIS,PYTHON}_VERSION}:string

RAILS_ENV

冒頭で触れた RAILS_ENV の例です。

_rails_env
#compdef -value-,RAILS_ENV,-default-

if [[ -d config/environments ]]; then
    compadd -- config/environments/*.rb(:t:r)
else
    compadd -- production development test
fi

なお、 RAILS_ENV 環境変数を使う代わりに rails コマンドの -e オプションを使う手もあります。ただ、 rails の補完定義を頑張って作るよりも手間が少なく、Node.jsの NODE_ENV やLaravelの APP_ENV にも応用が利きます。

RBENV_VERSION

Rubyのバージョン管理にrbenvを使っていて、その場限りでバージョンを切り替えたいときに使います。nodenv (NODENV_VERSION)なども同様です。

_rbenv_version
#compdef -value-,RBENV_VERSION,-default-

compadd -- $(rbenv versions --bare)

ASDF_NODEJS_VERSION, ASDF_POSTGRES_VERSION, etc.

asdfのように無数のプラグインがあってそれぞれ環境変数がある場合は、パターンマッチで定義して動的に処理します。

_asdf_lang_version
#compdef -P -value-,ASDF_*_VERSION,-default-

compadd -- $(asdf list ${(L)compstate[parameter][6,-9]} 2>/dev/null)

調べ方

だいたいはzshのマニュアルページに載っています。 man zshall で引いてみましょう。 fake-parameters についての記載はこのセクション-value- についての記載はこのセクションにあります。

あとは、zsh標準で用意されている補完定義にヒントが詰まっています。たとえば、 LANG, LC_MESSAGES, LC_TIME, LC_ALL などの値に標準でlocale名を補完できるようになっていますが、これは _locales で定義されています。手元でgrep (ag, rg, …)してみると、どんな補完定義があるのか一望できます。

grep -r '^#compdef' $fpath

Discussion