内容简介:编者按:本文作者李松峰,资深技术图书译者,翻译出版过40余部技术及交互设计专著,现任360奇舞团Web前端开发资深专家,360前端技术委员会委员、W3C AC代表。提到环境变量,我们都知道但是,除了这些,你对环境变量还知道多少?本文将带领大家全面了解一下环境变量。
编者按:本文作者李松峰,资深技术图书译者,翻译出版过40余部技术及交互设计专著,现任360奇舞团Web前端开发资深专家,360前端技术委员会委员、W3C AC代表。
提到环境变量,我们都知道 PATH 中包含着常用可执行文件的路径,有了它在命令行程序中直接输入文件名就可以运行程序。在Node.js环境中,我们也经常使用 process.env.NODE_ENV 来区分开发和线上环境,比如在开发环境下可以打印日志、不压缩资源,以方便调试。
但是,除了这些,你对环境变量还知道多少?本文将带领大家全面了解一下环境变量。
一点背景
我们现在使用的环境变量是在1979年Version 7 Unix中成型的,此后所有Unix系统包括 Linux 和macOS都实现了同样的特性。1982年,从PC DOS 2.0起,所有Windows操作系统也包含了环境变量,只是语法、用法和标准变量名有所差异。
环境变量是与进程紧密相关的一个特性。进程是什么?通俗地理解,进程就是“进行中的程序”,书面说法则是“运行中程序的一个实例”。意思其实都一样。之所以要抽象出进程这个概念,部分原因是为了隐藏使用CPU和内存资源的复杂度。有了进程的概念,一个程序在运行时就好像可以占有全部CPU和内存一样,不必考虑其他同时运行的程序。而每个程序都是在一定的上下文中运行,这个上下文中包含了程序正确运行所需的状态。这里所说的状态包括程序的代码、数据、栈、通用寄存器的内容、程序计数器、环境变量及打开的文件描述符,等等。
访问环境变量
环境变量可以在脚本中使用,也可以在命令行中使用。通常需要在变量名前面或两侧添加特殊符号来引用某个环境变量。比如,要显示用户的主目录,在大多数脚本环境中必须使用:
echo $HOME
在DOS/Windows命令行解释器(如 cmd.exe )中,要这样写:
ECHO %HOME%
在Windows PowerShell中,则要这样写:
Write-Output $env:HOMEPATH
命令行程序有3个内置命令,可以列出环境变量及它们的值:
-
env
-
set
-
printenv
在Unix和类Unix系统中,环境变量区分大小写。
fork,exec
在Unix中,环境变量通常在系统启动时由初始化脚本进行初始化,然后由系统中的所有其他进程继承。同样,当在一个程序中打开另一个程序时,调用程序会先复制一个与自身完全一样的进程,即子进程。子进程可以根据需要修改环境变量。最后,子进程再通过执行被调用的程序来覆盖自己。其中,复制进程对应 fork ,执行程序对应 exec :
-
fork:是由操作系统内核实现的系统调用,用于创建当前进程自身的一个副本;
-
exec:同样是由操作系统内核实现的系统调用,用于在已有进程的上下文中运行一个可执行文件。
exec在实际实现中通常是一组函数的代称,比如在 C语言 中就有 execl 、 execle 、 execlp 、 execv 、 execve 和 execvp 等几个函数,它们共用 exec 这个名字,只是分别在末尾追加了一两个字母,表示自己接受的参数不同(比如, e 表示接收环境变量的指针数组, l 表示一个一个地接收命令行参数,即参数列表, p 表示使用 PATH 环境变量查找文件, v 表示接收命令行参数的指针数组或者叫向量)。Linux内核只有一个叫 execve 的实现,前面所有其他的函数都是在用户空间中对这个系统调用的封装。
下面我们通过分析 execve 来理解父进程在创建子进程时如何传递环境变量。先看一看 execve 的接口:
#include <unistd.h>
int execve ( const char * pathname , char * const argv [ ] ,
char * const envp [ ] ) ;
(http://man7.org/linux/man-pages/man2/execve.2.html)
execve 函数接收3个参数,第一个是可执行文件的路径 pathname ,第二个是参数的指针数组 argv ,第三个是环境变量的指针数组 envp 。下图展示了参数数组的数据结构:
如图所示,变量 argv 指向一个NULL结尾的指针数组,前面的每个元素都是一个指向参数字符串的指针。按照约定, argv[0] 是可执行文件的名称。下面是环境变量指针数组的数据结构:
两上数据结构类似。唯一的区别是,环境变量数组元素指向的字符串都是名-值对形式的,比如 "PWD=/usr/droh" 。
在找到 pathname 对应的可执行文件后, execve 会调用操作系统永驻内存的 loader 代码,把可执行文件的代码和数据从磁盘复制到内存。然后,跳到其第一个指令或“入口点”开始执行该程序。这个过程叫加载。
加载之后,就是通过系统启动函数来运行用户的 main 函数:
int main(int argc, char *argv[], char *envp[]);
同样, main 函数也接收3个参数,其中最后一个参数 envp 就是新进程或子进程继承的环境变量。
命令行程序
命令行程序是我们最常用的启用其他程序的工具,因此会频繁地使用上述的 fork / exec 过程。比如:
node ./index.js
就会启用一个新的Node.js进程,运行index.js。与此同时,父进程的环境变量也会传递给这个新的子进程。
在Unix中,脚本或编译的程序修改的环境变量只会影响当前进程及其子进程。父进程及其他不相关的进程不受影响。
在命令行程序中,内置的 export 命令用来在当前进程中创建环境变量(自然也会被子进程继承):
export API_URL=http://example.com/api
不使用 export 关键字也可以创建变量,虽然以这种方式定义的变量可以通过 set 命令显示,但却 不是 真正的环境变量。这种变量只是命令行程序存储的,操作系统内核并不认。因此 env 和 printenv 命令不会显示它们,子进程也不会继承它们:
API_URL=http://example.com/api
然而,在命令行中调用其他程序时,在前面添加类似上面的变量赋值,则会将该变量添加到子进程的环境变量中:
API_URL=http://example.com/api node ./index.js
这样,index.js就可以通过 process.env.API_URL 取得传入的API地址了。
用户可以在自己所用的命令行 工具 的配置脚本(profile script)中添加或修改环境变量。
在DOS和Windows命令行解释器中,使用 SET 命令创建环境变量并赋值:
SET VAR_NAME=VALUE
只有 SET 命令会显示所有环境变量及它们的值。
hashbang
命令行脚本除了可以直接使用环境变量,还有一种常见的用法,就是“ hashbang ”。比如,下面这个脚本使用Node.js作为解释器:
#!/usr/bin/env node
console . log ( 'Hello, You Got It!' )
第一行中的 #!/usr/bin/env 是命令行程序内置 env 命令的完整路径,后面跟着运行当前脚本的程序名 node 。意思是在 env 的环境变量 PATH 中去查找解释程序 node 。
当然,不使用 env 而直接给出 node 的路径也是可以的:
#!/usr/local/bin/node
console . log ( 'Hello, You Got It!' )
但这样是不是就不灵活了?毕竟不同环境下 node 的位置可能不一样。使用 env 就可以做到在运行时再定位解释器的位置,从而让脚本的兼容性更好。
当然,使用 env 也有缺点:不同机器上 env 的位置同样可能不一样!
[完]
参考文献
-
https://en.wikipedia.org/wiki/Environment_variables
-
https://en.wikipedia.org/wiki/Env
-
Computer Systems A Programmer’s Perspective Third edition
关于奇舞周刊
《奇舞周刊》是360公司专业前端团队「 奇舞团 」运营的前端技术社区。关注公众号后,直接发送链接到后台即可给我们投稿。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。