godotenv
简介
twelve-factor应用提倡将配置存储在环境变量中,任何从开发环境切换到生产环境时需要修改的东西从代码抽取到环境变量中。但是在实际开发中,如果同一台机器运行多个项目,环境变量容易冲突。godotenv库从.env文件中读取配置,然后存储到程序的环境变量中,在代码中读取非常方便。godotenv源于Ruby开源项目dotenv
快速开始
在go module 中导入:
go get -u github.com/joho/godotenv latest
// quick-start/main.go
package main
import (
	"fmt"
	"log"
	"os"
	"github.com/joho/godotenv"
)
func main() {
	err := godotenv.Load()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("name:", os.Getenv("name"))
	fmt.Println("id:", os.Getenv("id"))
}
在当前目录创建.env文件:
name = dj
id = 110001
$ go run ./main.go
name: kesa
id: 110001
默认情况下,godotenv读取项目根目录下的.env文件,文件中使用key=value的格式,每行一个键值对;调用godotenv.Load即可加载,直接使用os.Getenv("key")即可读取
高级特性
自动加载
导入github.com/joho/godotenv/autoload,配置会自动读取(懒才是第一生产力 ヾ(≧▽≦*)o )
// auto-load/main.go
package main
import (
	"fmt"
	"os"
	_ "github.com/joho/godotenv/autoload"
)
func main() {
	fmt.Println("name:", os.Getenv("name"))
	fmt.Println("id:", os.Getenv("id"))
}
在导入的包名之前加_会执行包的init函数,使用_导入包只是为了使用包副作用(side-effect),不会导入包的其他内容
查看github.com/joho/godotenv/autoload:
// github.com/joho/godotenv/autoload/autoload.go
package autoload
/*
	You can just read the .env file on import just by doing
		import _ "github.com/joho/godotenv/autoload"
	And bob's your mother's brother
*/
import "github.com/joho/godotenv"
func init() {
	godotenv.Load()
}
n(*≧▽≦*)n
加载自定义文件
默认情况下加载.env文件,也可以加载任意名称的文件(不必.env结尾)
// godotenv/custom-env-file/main.go
package main
import (
	"fmt"
	"log"
	"os"
	"github.com/joho/godotenv"
	_ "github.com/joho/godotenv/autoload"
)
func main() {
	err := godotenv.Load("common", "dev.env", "production.env")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("App name:", os.Getenv("app_name"))
	fmt.Println("Version::", os.Getenv("version"))
	fmt.Println("Database:", os.Getenv("database"))
}
创建配置文件
common:
app_name = godotenv_note
version = 0.0.1
``dev.env`:
database = sqlite
production.env:
database = mysql
$ go run ./main.go
App name: godotenv_note
Version:: 0.0.1
Database: sqlite
Load读取配置时,若多个文件中出现相同的Key,先出现的优先读取,后续的Key不会生效
注释
.env文件可以添加注释,以#开始行尾结束
# app name
app_name = godotenv_note
# app version
version = 0.0.1
YAML
文件可以使用YAML格式:
# godotenv/custom-env-file/app_common.yml
# app name
app_name: godotenv_note
# app version
version: 0.0.1
// godotenv/yaml-env/main.go
package main
import (
	"fmt"
	"os"
	_ "github.com/joho/godotenv/autoload"
)
func main() {
	fmt.Println("App name:", os.Getenv("app_name"))
	fmt.Println("Version::", os.Getenv("version"))
}
$ go run ./main.go 
App name: godotenv_note
Version:: 0.0.1
不存入环境变量
godotenv可以不将配置存入环境变量,使用godotenv.Read返回map[string]string可直接使用:
// godotenv/read-env-directly/main.go
package main
import (
	"fmt"
	"log"
	"github.com/joho/godotenv"
	_ "github.com/joho/godotenv/autoload"
)
func main() {
	myEnv, err := godotenv.Read()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("App name:", myEnv["app_name"])
	fmt.Println("version:", myEnv["version"])
}
$ go run ./main.go 
App name: godotenv_note
version: 0.0.1
 从string和io.Reader中读取
godotenv可以直接从string中读取:
// godotenv/read-string/main.go
package main
import (
	"fmt"
	"log"
	"github.com/joho/godotenv"
)
func main() {
	content := `
app_name: godotenv_note@str
version: 0.0.1
`
	strEnv, err := godotenv.Unmarshal(content)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("App name:", strEnv["app_name"])
	fmt.Println("version:", strEnv["version"])
}
godotenv可以从实现了io.Reader接口(如os.File,net.Conn,bytes.Buffer等)的数据源中读取
// read-from-reader/main.go
package main
import (
	"bytes"
	"fmt"
	"github.com/joho/godotenv"
	_ "github.com/joho/godotenv/autoload"
	"log"
	"os"
)
func main() {
	file, _ := os.OpenFile(".env", os.O_RDONLY, 0666)
	myEnv, err := godotenv.Parse(file)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("app name:", myEnv["app_name"])
	fmt.Println("version:", myEnv["version"])
	buf := &bytes.Buffer{}
	buf.WriteString("app_name: godotenv_note@buffer")
	buf.WriteString("\n")
	buf.WriteString("version: 0.0.1")
	bufEnv, err := godotenv.Parse(buf)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("app name:", bufEnv["app_name"])
	fmt.Println("version:", bufEnv["version"])
}
注意从字符串读取使用Unmarshal,从io.Reader读取使用Parse
 生成.env文件
可以通过程序生成一个.env文件
// 
package main
import (
	"bytes"
	"github.com/joho/godotenv"
	"log"
)
func main() {
	// parse env
	buf := &bytes.Buffer{}
	buf.WriteString("app_name: app@env_generate\n")
	buf.WriteString("version: 0.0.1")
	bufEnv, _ := godotenv.Parse(buf)
	// save env
	err := godotenv.Write(bufEnv, "./.env")
	if err != nil {
		log.Fatal(err)
	}
}
查看文件:
$ cat .env
app_name="app@env_generate"
version="0.0.1"
也可以生成字符串,这里使用和解析字符串Unmarshal相对的Marshal:
// marshal-str/main.go
package main
import (
	"bytes"
	"fmt"
	"github.com/joho/godotenv"
	"log"
)
func main() {
	buf := &bytes.Buffer{}
	buf.WriteString("app: @buf\n")
	buf.WriteString("ver: 0.1")
	bufEnv, _ := godotenv.Parse(buf)
	content, err := godotenv.Marshal(bufEnv)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("String env: ", content)
}
多环境配置
在生产上一般会根据环境加载不同的配置文件
package main
import (
	"fmt"
	"github.com/joho/godotenv"
	"log"
	"os"
)
func main() {
	appEnv := os.Getenv("APP_ENV")
	if appEnv == "" {
		appEnv = "dev"
	}
	err := godotenv.Load(".env." + appEnv)
	if err != nil {
		log.Fatal(err)
	}
	// read basic env
	err = godotenv.Load()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("app:", os.Getenv("app"))
	fmt.Println("version:", os.Getenv("version"))
	fmt.Println("database:", os.Getenv("database"))
}
上例先读取环境变量APP_ENV来获取对应的配置.env.+env,最后读取默认的env文件
由于只有先读取到的值才生效,所以在.env配置中配置基础信息和默认值,在根据开发/测试/环境配置相关的值,写到对应的.env.dev/.env.test/.env.production
.env文件:
app: multi_env
version: 0.0.1
.env.dev文件:
database: sqlite3
.env.prd文件:
database: mysql
运行程序:
# 默认dev环境
$ go run ./main.go     
app: multi_env
version: 0.0.1
database: sqlite3
# prd环境
$ APP_ENV=prd go run ./main.go    
app: multi_env
version: 0.0.1
database: mysql
命令行
godotenv提供了命令行程序:
$ go get github.com/joho/godotenv/cmd/godotenv
$ godotenv -f ./.env COMMAND ARGS
程序godotenv会被安装在\$GOPAHT/bin中,将其加入$PATH即可全局调用
-f指定配置文件(默认为.env),示例如下:
// godotenv-cli
package main
import (
	"fmt"
	"os"
)
func main() {
	fmt.Println("app", os.Getenv("app"))
	fmt.Println("ver", os.Getenv("ver"))
}
$ godotenv -f ./.env go run main.go
app @cli
ver 0.1
源码
godotenv读取的是文件的内容,但是可以调用os.Getenv访问
func Load(filenames ...string) (err error) {
	filenames = filenamesOrDefault(filenames)
	for _, filename := range filenames {
		err = loadFile(filename, false)
		if err != nil {
			return // return early on a spazout
		}
	}
	return
}
func loadFile(filename string, overload bool) error {
	envMap, err := readFile(filename)
	if err != nil {
		return err
	}
	currentEnv := map[string]bool{}
	rawEnv := os.Environ()
	for _, rawEnvLine := range rawEnv {
		key := strings.Split(rawEnvLine, "=")[0]
		currentEnv[key] = true
	}
	for key, value := range envMap {
		if !currentEnv[key] || overload {
			os.Setenv(key, value)
		}
	}
	return nil
}
可以看到godotenv将配置通过os.Setenv设置到环境变量中了
总结
本文介绍了godotenv的基本和高级用法,在多环境开发中,可以考虑使用godotenv来进行环境切换
参考
- godotenv github repo
 - godotenv godocs
 - Go 每日一库之 godotenv darjun blog
 - Go Modules Reference go module
 
