Published on

dotenv(.env) Fileを現在のShellに反映(export)する方法

Authors
  • avatar
    Name
    グレッグ
    Twitter

Overview

ApplicationやDockerとかでは使えたけどなぜか、Shellで使おうとすると意図通り動かないことがある .env(DotEnv) File。

Shellから正しい読み込み方法身につけましょう。

DotEnvとは?

File形式

.env FileはDotEnvとして呼ばれています。

例えば下記のようなものです。

.env
APPLICATION_NAME=SA201_Website
APPLICATION_VERSION=v1

簡略にいえば、1行ずつKey/Value形式でValueがString型で宣言されているFileです。

このファイルはDockerやApplication等にパラメーターとして提供すれば必要な変数を纏めてLoadさせることが出来て非常に便利ですよね。

特にLinuxでは基本的に .env Fileが用意されていれば自動的に読み込んでくれるApplicationも存在するので開発の際に度々出会えると思います。

DotEnvを使わない場合

このFileが無い場合はどうなるかと言えば 、下記のようになるでしょう。

export APPLICATION_NAME=SA201_Website
export APPLICATION_VERSION=v1

良く考えれば単にexport が抜けているだけであり、変数宣言だけを狙ったFileです。 これを理解することがとっても大事です。

DotEnvを使う場面

Local、Staging、Production、Region、Country等同じApplicationでも実行する環境は様座なのでこのようなFileは結構必要になってきます。

最近はYamlファイル等で管理されることも多いですが、まだ良く使われるしParsingが簡単なので今後も度々出会えるものだと思います。

JavaならばProperty file(e.g. dev.property)も同じことを実施することも出来ますが、編集権限をだれが持つことになるかが大きな違いが出ます。

Property FileはApplicationの中にPackageされるのが一般的にです。 Application Package外に置くことも可能ですがApplicationにその場所を特定する必要があります。 なので、場合によってはこのProperty Fileを使うというのが少し難易度が上がります。

DotEnvの場合はShellに環境変数として宣言するためのApplicationとは個別の起動Processとして存在することが可能です。

Applicationが直接DotEnv FileをFileとしても読み込むことも可能ですし

echo OS_ENV_KEY_NAME=123 > ".env"
./app # appに.env Fileを自動的に読み込むように設定している 

System.getenv("OS_ENV_KEY_NAME) のようにGlobal Scopeで値を読み込むことにしてShellにその宣言を委託することも出来ます。

export OS_ENV_KEY_NAME=123
./app 

Deployment過程で考えれば、Container Task Definitionに宣言することで環境設定を行うことも可能になります。

docker-compose.yml
version: "3"
services:
 app:
   image: APP:latest
   env_file: #Applicationは.env File配置を気にしなくても済む
    - .env
   environment: #必要に応じて柔軟にOverrideも可能、または全てEnvironment Vairableとして宣言する
    - JAVA_OPTS=-Xms128m -Xmx1024m #-XX:+PrintFlagsFinal

上記のように必要に応じて柔軟に宣言を行うことが出来るため、Operation Teamと協業もしやすくなります。

一番の問題、宣言したけどなぜApplicationでは認識出来ない?

この便利なものが使い間違えて、使いづらいものになる場合があります。

一般的にShell Scriptを現在のShellに反映する場合は source file_name.sh という宣言で読み込むことが多いと思います。

ただ、.env fileはShellでは確認出来るけどなぜかApplicationではそれが読み込まれ無い時があります。

例えば次のApplicationがある場合

main.kt
val name = System.getenv("APPLICATION_NAME")
println("APPLICATION_NAME: $name")

下記のように実行してみると…

$ cat .env
APPLICATION_NAME=SA201_Website

$ source .env

$ echo "APPLICATION_NAME: $APPLICATION_NAME"
APPLICATION_NAME: SA201_Website

$ java -jar ... 

APPLICATION_NAME: null

Shellでは確かに変数として認識されたけど、Applicationにいくとそれが認識出来ない〜ということです。

原因はただ source .env だけだとLocal VariableとしてShellに格納されるだけだから、Applicationは別のProcessとして実行されるためLocal Variableの存在は分からないからですよね。

よって、態々 export 宣言より Local Variableでは無く Environment Variable として宣言する必要があることです。

$ export APPLICATION_NAME=SA201_Website

$ echo "APPLICATION_NAME: $APPLICATION_NAME"
APPLICATION_NAME: SA201_Website

$ java -jar ... 

APPLICATION_NAME: SA201_Website

は〜い。正常に出力出来ました。

こうなると .env Fileの活用が難しいですよね。対策は次の方法があります。

鍵は allexport

set -o allexport
source .env
set +o allexport

単に allexport オプションをShellに渡すだけです。 簡単ですよね。

allexportの定義

man bash

...
set [--abefhkmnptuvxBCHP] [-o option] [arg ...]

...
-a      Automatically mark variables and functions which are modified or created for export to the environment of subsequent commands.

...
-o option-name
    The option-name can be one of the following:
    allexport
      Same as -a.   
...

明確に for export to the environment of subsequent commands.って書いてますね。

zshや他のShellでもset commandは同様に使えるのでShell全般的な使い方として覚えても問題は無いと思います。

もう一度Applicationを起動してみましょう

$ cat .env
APPLICATION_NAME=SA201_Website

$ set -o allexport && source .env && set +o allexport

$ java -jar ... 

APPLICATION_NAME: SA201_Website

Deployment Processにおいてはあんまり使わなかったり、Containerを立ち上げて作業するとかだとあんまり使うことは無いかも知れません。 なぜなら前で説明した通り、OS Levelで事前宣言を行うからです。

どちらかというとLocalで開発する際には開発者個人のShellにこのDotEnvをLoadingする必要があるので、ちょっとしたTechniqueが必要だった訳ですね。

私がLinuxのJuniorだった時に、これが良く理解出来ず数時間ハマった覚えがあります。

allexportの副作用に関する考え、Spaceに注意

上記の定義にも関わらずこの使い方は

  • 注意しないといけない
  • そもそもDotEnvのために使うものでは無い

という声もあるようです。

一部例を確認したところ私の考えはこうです。

Environment VariableにFunctionが入っているから発生する問題なだけ

DotEnvは標準的なものでも無いですし、そのため宣言する文字列の範囲を明示的に宣言出来るSyntaxが決まっている訳でも無いですね。

なので組み合わせが発生したり、特集文字が入ったりするとShellのParsingによって意図しない文字列がEnvironment Variableとして残る可能性が十分にあります。

例えば下記のようにことですね。

.env
ENV=staging
URL=${ENV}.app-name.com
DB_PASSWORD=a4923o"@5! ew
DB_NAME=app-db | app2-db    #NG Case
PAYMENT_GATEWAY=her > df    #NG Case

基本的にSpaceが入って、ShellのCommandとして実行出来るような文字列はNGということです。

良く考えれば source Command自体は拡張子関係無くShell Scriptとして読み込む訳ですから、Shell Commandが混じっていたら実行してしまうのは当たり前ですよね。

簡単に考えればSpaceだけ気を付ければ良いということにもなります。

使い方の間違いで使うことをやめさせるのは勿体ないことだと思いますね。

正しいEnvironment Variablesの宣言の仕方

Wiki(Environment_variable)にはこう書かれてました。

Assignment: Unix
The commands env and set can be used to set environment variables and are often incorporated directly into the shell.

The following commands can also be used, but are often dependent on a certain shell.

VARIABLE=value         # (there must be no spaces around the equals sign)
export VARIABLE        # for Bourne and related shells
export VARIABLE=value  # for ksh, bash, and related shells
setenv VARIABLE value  # for csh and related shells

=記号の前後にSpaceが入るのはNG~ということだけ気をつければ良さそうです。

ShellによってはExportが宣言Commandでは無いため、allexport Optionも使えない場合もあると思うのでご注意下さい。 一般的にはBashのようなShellを使うのが一般的だと思うのであんまり遭遇しない問題だと思います。