direnvを使用して、同名のコマンドの挙動をディレクトリ毎に変更したい
2018/12/282020/11/17
IT docker

概要

同じコマンド名でもディレクトリ毎に異なる挙動をさせたかった。具体的な使用例は本記事後半を参照のこと。

おおざっぱに言うと、ディレクトリ毎にエイリアスを変更するイメージ。

最終的にはdirenvを使用して実現できた。

direnvの準備

direnvは、所定のディレクトリ以下に固有の環境変数を設定してくれるツール。

インストール

Macであれば、以下でインストール可能。

1
brew install direnv

インストール後、使用しているシェルの設定ファイルにdirenvの設定を追加する

zshなら ~/.zshrc に以下を追加。

1
eval "$(direnv hook zsh)"

bashなら ~/.bashrc に以下を追加。

1
eval "$(direnv hook bash)"

direnvの簡単な使い方

インストール後は、固有の環境変数を設定したいディレクトリで以下のコマンドを実行して、環境変数を設定すれば良い。

1
direnv edit .

例えば、以下のように記載するとそのディレクトリ配下では HELLO という環境変数に World という値が設定されている。

1
export HELLO='World'

設定ファイルはそのディレクトリに、 .envrc というファイルが作成される。

.envrc は直接編集するのではなく direnv edit . を用いて編集する。直接編集した場合は、 direnv allow を実行しろというエラーが表示されるので、その通りに実行すること。

direnvでディレクトリ固有のコマンドを定義する設定

まずは、 ~/.direnvrc に以下を設定する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ALIASES_DIR=".direnv/aliases"

export_function() {
  local name=$1
  local alias_dir=$PWD/${ALIASES_DIR}
  mkdir -p "$alias_dir"
  PATH_add "$alias_dir"
  local target="$alias_dir/$name"
  if declare -f "$name" >/dev/null; then
    echo "#!/usr/bin/env bash" > "$target"
    declare -f "$name" >> "$target" 2>/dev/null
    echo "$name" '"$@"' >> "$target"
    chmod +x "$target"
  fi
}

clear_direnv_aliases() {
  local alias_dir=$PWD/${ALIASES_DIR}
  rm -rf $alias_dir/*
}

export_function は第1引数に作成したいコマンド名を渡すと、そのコマンド名のファイルが実行権限付きで ALIASES_DIR のパス以下に作成される。 .envrc 内で export_function が呼び出されれば、このディレクトリ配下にいる間 ALIASES_DIR が環境変数PATHに追加された状態になる。

定義したいコマンドファイルの中身を以下のように .envrc 内に定義する。

1
2
3
4
5
6
7
clear_direnv_aliases

sample-cmd () {
    echo "Hello $1"
    echo "This command is sample command"
}
export_function sample-cmd

これにより、 .direnv/aliases/sample-cmd という実行権限付きのファイルが以下の内容で作成されている。

1
2
3
4
5
6
7
#!/usr/bin/env bash
sample-cmd ()
{
    echo "Hello $1";
    echo "This command is sample command"
}
sample-cmd "$@"

このコマンドを実行すれば以下のようになる。引数を受け取ることも出来る。

1
2
3
4
5
6
7
$ sample-cmd
Hello
This command is sample command

$ sample-cmd Mike
Hello Mike
This command is sample command

clear_direnv_aliasesexport_function で作成されたコマンドを全て削除する関数であり、これを .envrc で呼び出せば、direnv対象のディレクトリに入る度にコマンドファイルが削除される。 これを入れておかないと、 .envrc で一度作成したコマンドを削除(又は変更)した場合でも前のコマンドがそのまま残り続けてしまう。 clear_direnv_aliases は 最初の export_function よりも前に記載しておくこと。

使用例

例えば、色々なプロジェクトにおいて相違するdockerの起動、終了などのコマンドを同じコマンド名に統一するために使用している。

下記プロジェクトAのようにシンプルな感じであれば良いのだが、プロジェクトBのようにdocker-composeファイルのファイル名を指定するよう場合には、いちいち面倒くさい。プロジェクトによってはこのような面倒くさいコマンドが必要なものはプロジェクトCのようにMakefileが用意され、面倒なコマンドを隠蔽してくれている場合もある。

やりたい事 プロジェクトAの例 プロジェクトBの例 プロジェクトCの例
起動 docker-compose up -d docker-compose -f docker-compose.local.yaml up -d make up
コンテナをkillして終了 docker-compose down docker-compose -f docker-compose.local.yaml down make down
volumeなども一緒に削除して終了 docker-compose down -v –remove-orphans docker-compose -f docker-compose.local.yaml down -v –remove-orphans make clean

プロジェクトBのような場合には、自分がMakefileを追加してPRを投げれば良いが、それでも、全プロジェクトでMakefileのターゲット名を統一できるわけではない。 各プロジェクトでやりたい事に対して同じコマンドを利用するにはaliasでの対応は難しい。 このようなケースでは今回のような対応が有効だ。

今は以下のような共通コマンドを設定して利用している。

やりたい事 共通コマンド
コンテナのログを表示 dc-logs
コンテナをkillして終了 dc-down
Volumeなども一緒に削除して終了 dc-hard-down
コンテナをkillして再起動 & ログを表示 dc-restart
Volumeなどを削除して再起動 & ログを表示 dc-hard-restart

.envrc 設定の一例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
clear_direnv_aliases

dc-logs () {
    docker-compose logs -f "$@"
}
export_function dc-logs

dc-down () {
    docker-compose down
}
export_function dc-down

dc-hard-down () {
    docker-compose down -v --remove-orphans
}
export_function dc-hard-down

dc-restart () {
    dc-down && make up && dc-logs
}
export_function dc-restart

dc-hard-restart () {
    dc-hard-down && make up && make init && dc-logs
}
export_function dc-hard-restart

コンテナを起動していないときも dc-restartdc-hard-restart を実行すれば良い。

同じことをやるときのインターフェイスを統一しておくと無駄に頭を使わなくて済み開発に集中できるようになる。

なお、dc-logs は外部から引数を受け付けられるようにコマンドを定義しているため、以下のようにログを確認する対象のコンテナをコマンド実行時に切り替えられる。

参考サイト