比较在 shell 中两种不同的环境变量设置方式

直接设置环境变量和使用 export 设置有什么不同?

在 shell 中,有两种设置环境变量的方式:直接设置和使用 export 命令。

直接设置环境变量

直接设置环境变量的方式是将变量名和值用等号连接起来,例如:

1
ENV=1

这种方式设置的环境变量只在当前 shell 会话中有效,当会话结束时,变量也会被删除。

验证:

输入

1
ENV=1 showenv | grep ENV

输出:

1
envp[53] = "ENV=1"

再次输入

1
showenv | grep ENV

输出:

可以看到,直接设置的环境变量只在当前 shell 会话中有效。

使用 export 设置环境变量

使用 export 命令设置环境变量的方式是将变量名和值用等号连接起来,并在变量名前加上 export,例如:

1
export ENV=1

这种方式设置的环境变量不仅只在当前 shell 会话中有效,还会被传递给子进程 。也就是说,当我在当前 shell 会话中设置了一个环境变量,然后在子进程中执行某个命令,这个命令就可以使用这个环境变量。

验证:

输入

1
2
export ENV=1
showenv | grep ENV

输出:

1
envp[53] = "ENV=1"

再次输入

1
showenv | grep ENV

输出:

1
envp[53] = "ENV=1"

可以看到,使用 export 命令设置的环境变量不仅只在当前 shell 会话中有效,还会被传递给子进程。

延伸

之所以会来思考两种不同的环境变量设置方式这个问题,是因为在跑我之前的 ToDolist 后端时发现,当时为了 Docker,在 application.properties 里把数据库的连接写成了 spring.datasource.url=jdbc:mysql://${MYSQL_URL:mysqldb}/${MYSQL_DB:todolistdb},我现在想在本地跑的话,就要传入 MYSQL_URL 的值。

一开始天真地想用 export 环境变量传入,结果当然还是 idea 连不上数据库。后来咨询专业人士,结合相关文档的阅读,收获了以下的一些小知识。

  1. 对于我这个项目, MYSQL_URL MYSQL_USERNAME MYSQL_PASSWORD 这些可以直接在代码里写死(后面在不同服务器上跑,要反复修改代码,非常不方便,直接 pass),也可以用 args(每次启动都要输入一遍多个参数,也很麻烦),一般都是放在环境变量里。

  2. 需要在 idea 里的 Run/Debug Configurations 里设置 Environment variables。我自己在一个 shell 进程里设置了环境变量,idea 不是 shell 开的,它不知道 shell 有什么环境变量,也不知道我设置的是在哪个进程,对于它来说读取比较麻烦,所以需要专门在 idea 里面进行设置。

  3. 那么问题又来了,每个 shell 进程里的环境变量是不一样的吗?关了一个进程后,再开新的进程里为什么会有我之前设置的环境变量?

    答案是 execve

    execve 函数的定义如下:

    1
    2
    
    int execve(const char *pathname, char *const _Nullable argv[],
                      char *const _Nullable envp[]);
    

    一个 shell 进程会用 execve 来启动用户请求的命令。当我在命令行输入 export ENV=1 命令并按下回车时, shell 会解析命令并将其分解为命令本身和参数,调用 fork 系统调用创建一个新的子进程,在子进程中, shell 会使用 execve 来加载并执行输入的命令,像我的环境变量就通过 envp 传递,当命令执行完毕后,子进程会退出,并返回执行结果。

    也就是说,当我启动新的进程的时候,shell 帮我复制到 execveenvp 参数上了,新开的进程就有了这些环境变量。

Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy