先前讲过,exec
系统调用执行新程序时会把命令行参数和环境变量表传递给main
函数,它们在整个进程地址空间中的位置如下图所示。
和命令行参数argv
类似,环境变量表也是一组字符串,如下图所示(该图出自[APUE2e])。
libc
中定义的全局变量environ
指向环境变量表,environ
没有包含在任何头文件中,所以在使用时要用extern
声明。例如:
例 30.1. 打印环境变量
#include <stdio.h> int main(void) { extern char **environ; int i; for(i=0; environ[i]!=NULL; i++) printf("%s\n", environ[i]); return 0; }
执行结果为
$ ./a.out SSH_AGENT_PID=5717 SHELL=/bin/bash DESKTOP_STARTUP_ID= TERM=xterm ...
由于父进程在调用fork
创建子进程时会把自己的环境变量表也复制给子进程,所以a.out
打印的环境变量和Shell进程的环境变量是相同的。
按照惯例,环境变量字符串都是name=value
这样的形式,大多数name
由大写字母加下划线组成,一般把name
的部分叫做环境变量,value
的部分则是环境变量的值。环境变量定义了进程的运行环境,一些比较重要的环境变量的含义如下:
可执行文件的搜索路径。ls
命令也是一个程序,执行它不需要提供完整的路径名/bin/ls
,然而通常我们执行当前目录下的程序a.out
却需要提供完整的路径名./a.out
,这是因为PATH
环境变量的值里面包含了ls
命令所在的目录/bin
,却不包含a.out
所在的目录。PATH
环境变量的值可以包含多个目录,用:
号隔开。在Shell中用echo
命令可以查看这个环境变量的值:
$ echo $PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games
当前Shell,它的值通常是/bin/bash
。
当前终端类型,在图形界面终端下它的值通常是xterm
,终端类型决定了一些程序的输出显示方式,比如图形界面终端可以显示汉字,而字符终端一般不行。
语言和locale,决定了字符编码以及时间、货币等信息的显示格式。
当前用户主目录的路径,很多程序需要在主目录下保存配置文件,使得每个用户在运行该程序时都有自己的一套配置。
用environ
指针可以查看所有环境变量字符串,但是不够方便,如果给出name
要在环境变量表中查找它对应的value
,可以用getenv
函数。
#include <stdlib.h> char *getenv(const char *name);
getenv
的返回值是指向value
的指针,若未找到则为NULL
。
修改环境变量可以用以下函数
#include <stdlib.h> int setenv(const char *name, const char *value, int rewrite); void unsetenv(const char *name);
putenv
和setenv
函数若成功则返回为0,若出错则返回非0。
setenv
将环境变量name
的值设置为value
。如果已存在环境变量name
,那么
若rewrite非0,则覆盖原来的定义;
若rewrite为0,则不覆盖原来的定义,也不返回错误。
unsetenv
删除name
的定义。即使name
没有定义也不返回错误。
例 30.2. 修改环境变量
#include <stdlib.h> #include <stdio.h> int main(void) { printf("PATH=%s\n", getenv("PATH")); setenv("PATH", "hello", 1); printf("PATH=%s\n", getenv("PATH")); return 0; }
$ ./a.out PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games PATH=hello $ echo $PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games
可以看出,Shell进程的环境变量PATH
传给了a.out
,然后a.out
修改了PATH
的值,在a.out
中能打印出修改后的值,但在Shell进程中PATH
的值没变。父进程在创建子进程时会复制一份环境变量给子进程,但此后二者的环境变量互不影响。