引言
今天在写shell脚本的时候,遇到一个棘手的问题,是关于环境变量,当时父进程的环境变量是无论如何也无法传递给子进程,重重排查,才找到问题所在,所以心血来潮,想写一下关于Linux下的环境变量具有全局属性的这个问题。
之前写过一个shell脚本,今天以这个脚本为例,感兴趣的可以去看一下命令行解释器,另外,关于环境变量,建议也看一下Linux·下环境变量。
解释一下下面我们具体在做什么:实验我们自己编写的shell脚本,是否可以设置环境变量?环境变量是否具有全局属性?
- 首先我们自己编写了一个简单的shell脚本 --- 命令行解释器 (myshell.c)
- 其次,现在我们想设置一个环境变量(注:环境变量具有全局属性,这意味着任何的程序都能调用)
- 具体操作是往shell脚本中添加 export 指令,使得我们在命令行下,可以设置环境变量
- 并且运行其他程序使用这个环境变量 (test.c)
myshell.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h> //保存完整的命令行字符串 -- 充当缓冲区 #define NUM 1024 char cmd_line[NUM]; //保存切割之后的字符串 #define SIZE 32 char* g_argv[SIZE]; #define SEP " " int main() { //0.命令行解释器,一定是一个常驻内存的进程 --- 不退出(死循环) while(1) { //1打印出提示信息 -- [wxq@VM-4-9-centos shell]$ printf("[dwr@VM-1-1-test shell]$ "); fflush(stdout); //2.获取用户的键盘输入[输入的是各种指令和选项"ls -a -l"] memset(cmd_line, 0, sizeof cmd_line); if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL) { continue; } //输入的回车键设为\0 ls -a -l \n \0 cmd_line[strlen(cmd_line)-1] = '\0'; // printf("echo:%s\n", cmd_line); //debug //3.命令行字符串进行解析 "ls -a -l -s" ---> "la" "-a" "-l" "-s" g_argv[0] = strtok(cmd_line, SEP); //第一次调用,传入原始的字符串 int index = 1; while(g_argv[index++] = strtok(NULL, SEP)); //第二次调用,如果还要分割原始字符串,传入NULL //简单配置ls的颜色 int i = 1; if(strcmp(g_argv[0], "ls") == 0) { g_argv[i++] = "--color=auto"; } //识别别名 - 主要是测试,一般是有接口的 if(strcmp(g_argv[0], "ll") == 0) { g_argv[0] = "ls"; g_argv[i++] = "-l"; g_argv[i++] = "--color=auto"; } //4.TODO 内置命令:让父进程(shell)自己执行的命令。我们叫做内置命令,内建命令 //内建命令本质就是shell中的一个函数调用 if(strcmp(g_argv[0], "cd") == 0) //not child execute; father execute; { if(g_argv[1] != NULL) { chdir(g_argv[1]); //cd cd .. } continue; } //5.创建子进程进行程序替换 pid_t id = fork(); //child if(id == 0) { execvp(g_argv[0],g_argv); exit(-1); } //father int status = 0; pid_t ret = waitpid(id, &status, 0); //阻塞等待 if(ret > 0) { printf("exit code:%d\n",WEXITSTATUS(status)); } } return 0; }
test.c
[wxq@VM-4-9-centos shell2]$ touch test.c [wxq@VM-4-9-centos shell2]$ vim test.c [wxq@VM-4-9-centos shell2]$ cat test.c #include <stdio.h> #include <stdlib.h> int main() { printf("hello test!\n"); printf("myenv: %s\n", getenv("MYVAL")); return 0; } [wxq@VM-4-9-centos shell2]$ gcc -o test test.c [wxq@VM-4-9-centos shell2]$ ./test hello test! myenv: (null) [wxq@VM-4-9-centos shell2]$
目前 MYVAL 这个环境变量我们是未定义的,所以这里需要在脚本内添加指令export的逻辑。
正文开始:
此时传入的环境变量为空,现在我们切换到shell脚本中
[wxq@VM-4-9-centos shell2]$ ./myshell [dwr@VM-1-1-test shell]$ ./test hello test! myenv: (null) exit code:0 [dwr@VM-1-1-test shell]$
运行发现,这里也是空,因为我们目前还没有定义这个环境变量,现在我们增加指令export的逻辑。
逻辑:"export MAVAL=1024"
按照我们shell脚本的逻辑,这一行代码会被进行切割 "export" "MAVAL=1024" 这样的两个子串
同时我们设置了两个缓冲区,一个存放完整的字符串cmd_line[ ], 一个存放切割后的子串g_argv[ ]
如果g_argv[0] == export 就意味着用户在命令行输入了这个指令,此时,我们需要把后面的子串g_argv[1]设置成环境变量(需要调用一个接口 putenv:更改或添加环境变量)
实现如下:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h> //保存完整的命令行字符串 -- 充当缓冲区 #define NUM 1024 char cmd_line[NUM]; //保存切割之后的字符串 #define SIZE 32 char* g_argv[SIZE]; #define SEP " " int main() { //0.命令行解释器,一定是一个常驻内存的进程 --- 不退出(死循环) while(1) { //1打印出提示信息 -- [wxq@VM-4-9-centos shell]$ printf("[dwr@VM-1-1-test shell]$ "); fflush(stdout); //2.获取用户的键盘输入[输入的是各种指令和选项"ls -a -l"] memset(cmd_line, 0, sizeof cmd_line); if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL) { continue; } //输入的回车键设为\0 ls -a -l \n \0 cmd_line[strlen(cmd_line)-1] = '\0'; // printf("echo:%s\n", cmd_line); //debug //3.命令行字符串进行解析 "ls -a -l -s" ---> "la" "-a" "-l" "-s" g_argv[0] = strtok(cmd_line, SEP); //第一次调用,传入原始的字符串 //export MYVAL=1024 if(strcmp(g_argv[0], "export") == 0 && g_argv[1] != NULL) { putenv(g_argv[1]); continue; } int index = 1; while(g_argv[index++] = strtok(NULL, SEP)); //第二次调用,如果还要分割原始字符串,传入NULL //简单配置ls的颜色 int i = 1; if(strcmp(g_argv[0], "ls") == 0) { g_argv[i++] = "--color=auto"; } //识别别名 - 主要是测试,一般是有接口的 if(strcmp(g_argv[0], "ll") == 0) { g_argv[0] = "ls"; g_argv[i++] = "-l"; g_argv[i++] = "--color=auto"; } //4.TODO 内置命令:让父进程(shell)自己执行的命令。我们叫做内置命令,内建命令 //内建命令本质就是shell中的一个函数调用 if(strcmp(g_argv[0], "cd") == 0) //not child execute; father execute; { if(g_argv[1] != NULL) { chdir(g_argv[1]); //cd cd .. } continue; } //5.创建子进程进行程序替换 pid_t id = fork(); //child if(id == 0) { execvp(g_argv[0],g_argv); exit(-1); } //father int status = 0; pid_t ret = waitpid(id, &status, 0); //阻塞等待 if(ret > 0) { printf("exit code:%d\n",WEXITSTATUS(status)); } } return 0; }
此时我们进入shell脚本当中,尝试设置环境变量,但是会发现结果不对劲,如下
报错1:
[wxq@VM-4-9-centos shell2]$ ./myshell [dwr@VM-1-1-test shell]$ export MYVAL=1024 exit code:255 [dwr@VM-1-1-test shell]$
分析了一下,发现出现这样的报错,是因为代码的逻辑不对:如下

这里是没有进入if语句的,因为第二次切割子串在 if 语句之后, g_argv[1] 为空,条件不成立,未进入语句,继续执行下面的代码,创建子进程进行程序替换,但是替换时export这样的命令是不存在的(我们编写的shell脚本内),所以出现了报错 。而我们期望的是这里设置环境变量后会continue跳出while循环,进入下一次的循环。
那我们修改一下顺序,继续尝试:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h> //命令行解释器 //shell 运行原理:通过让子进程执行命令,父进程等待&&解析命令 //保存完整的命令行字符串 -- 充当缓冲区 #define NUM 1024 char cmd_line[NUM]; //保存切割之后的字符串 #define SIZE 32 char* g_argv[SIZE]; #define SEP " " int main() { //0.命令行解释器,一定是一个常驻内存的进程 --- 不退出(死循环) while(1) { //1打印出提示信息 -- [wxq@VM-4-9-centos shell]$ printf("[dwr@VM-1-1-test shell]$ "); fflush(stdout); //2.获取用户的键盘输入[输入的是各种指令和选项"ls -a -l"] memset(cmd_line, 0, sizeof cmd_line); if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL) { continue; } //输入的回车键设为\0 ls -a -l \n \0 cmd_line[strlen(cmd_line)-1] = '\0'; // printf("echo:%s\n", cmd_line); //debug //3.命令行字符串进行解析 "ls -a -l -s" ---> "la" "-a" "-l" "-s" g_argv[0] = strtok(cmd_line, SEP); //第一次调用,传入原始的字符串 int index = 1; while(g_argv[index++] = strtok(NULL, SEP)); //第二次调用,如果还要分割原始字符串,传入NULL //export MYVAL=1024 if(strcmp(g_argv[0], "export") == 0 && g_argv[1] != NULL) { putenv(g_argv[1]); continue; } //简单配置ls的颜色 int i = 1; if(strcmp(g_argv[0], "ls") == 0) { g_argv[i++] = "--color=auto"; } //识别别名 - 主要是测试,一般是有接口的 if(strcmp(g_argv[0], "ll") == 0) { g_argv[0] = "ls"; g_argv[i++] = "-l"; g_argv[i++] = "--color=auto"; } //4.TODO 内置命令:让父进程(shell)自己执行的命令。我们叫做内置命令,内建命令 //内建命令本质就是shell中的一个函数调用 if(strcmp(g_argv[0], "cd") == 0) //not child execute; father execute; { if(g_argv[1] != NULL) { chdir(g_argv[1]); //cd cd .. } continue; } //5.创建子进程进行程序替换 pid_t id = fork(); //child if(id == 0) { execvp(g_argv[0],g_argv); exit(-1); } //father int status = 0; pid_t ret = waitpid(id, &status, 0); //阻塞等待 if(ret > 0) { printf("exit code:%d\n",WEXITSTATUS(status)); } } return 0; }
运行如下:
[wxq@VM-4-9-centos shell2]$ ./myshell
[dwr@VM-1-1-test shell]$ export MYVAL=1024
[dwr@VM-1-1-test shell]$ ./test
hello test!
myenv: (null)
exit code:0
[dwr@VM-1-1-test shell]$
此时是可以设置环境变量的,但是这里test程序并没有拿到这个环境变量,那是因为什么呢?继续向下分析:
做个测试,获取putenv的返回值,看看是否真的设置了环境变量:
RETURN VALUE
The putenv() function returns zero on success, or nonzero if an error occurs. In the event of an error, errno is set to indicate the cause.putenv()函数在成功时返回零,如果发生错误则返回非零
测试如下:
//export MYVAL=1024 if(strcmp(g_argv[0], "export") == 0 && g_argv[1] != NULL) { int ret = putenv(g_argv[1]); if(ret == 0) printf("%s putenv success \n", g_argv[1]); continue; }
运行:
[wxq@VM-4-9-centos shell2]$ ./myshell [dwr@VM-1-1-test shell]$ export MYVAL=1024 MYVAL=1024 putenv success [dwr@VM-1-1-test shell]$ ./test hello test! myenv: (null) exit code:0 [dwr@VM-1-1-test shell]$
这里我们发现,的确是设置成功了环境变量,但是为什么其他程序无法调用呢?继续向下分析:
再次进行测试,这里我们成功设置了环境变量后,手动去传递:声明 extern char** environ ,然后子进程手动传入进去
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h> //命令行解释器 //shell 运行原理:通过让子进程执行命令,父进程等待&&解析命令 //保存完整的命令行字符串 -- 充当缓冲区 #define NUM 1024 char cmd_line[NUM]; //保存切割之后的字符串 #define SIZE 32 char* g_argv[SIZE]; #define SEP " " extern char** environ; int main() { //0.命令行解释器,一定是一个常驻内存的进程 --- 不退出(死循环) while(1) { //1打印出提示信息 -- [wxq@VM-4-9-centos shell]$ printf("[dwr@VM-1-1-test shell]$ "); fflush(stdout); //2.获取用户的键盘输入[输入的是各种指令和选项"ls -a -l"] memset(cmd_line, 0, sizeof cmd_line); if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL) { continue; } //输入的回车键设为\0 ls -a -l \n \0 cmd_line[strlen(cmd_line)-1] = '\0'; // printf("echo:%s\n", cmd_line); //debug //3.命令行字符串进行解析 "ls -a -l -s" ---> "la" "-a" "-l" "-s" g_argv[0] = strtok(cmd_line, SEP); //第一次调用,传入原始的字符串 int index = 1; while(g_argv[index++] = strtok(NULL, SEP)); //第二次调用,如果还要分割原始字符串,传入NULL //export MYVAL=1024 if(strcmp(g_argv[0], "export") == 0 && g_argv[1] != NULL) { int ret = putenv(g_argv[1]); if(ret == 0) printf("%s putenv success \n", g_argv[1]); continue; } //简单配置ls的颜色 int i = 1; if(strcmp(g_argv[0], "ls") == 0) { g_argv[i++] = "--color=auto"; } //识别别名 - 主要是测试,一般是有接口的 if(strcmp(g_argv[0], "ll") == 0) { g_argv[0] = "ls"; g_argv[i++] = "-l"; g_argv[i++] = "--color=auto"; } //debug // for(index = 0 ; g_argv[index]; index++) // { // printf("g_argv[%d]: %s\n", index, g_argv[index]); // } //4.TODO 内置命令:让父进程(shell)自己执行的命令。我们叫做内置命令,内建命令 //内建命令本质就是shell中的一个函数调用 if(strcmp(g_argv[0], "cd") == 0) //not child execute; father execute; { if(g_argv[1] != NULL) { chdir(g_argv[1]); //cd cd .. } continue; } //5.创建子进程进行程序替换 pid_t id = fork(); //child if(id == 0) { // execvp(g_argv[0],g_argv); execvpe(g_argv[0], g_argv, environ); exit(-1); } //father int status = 0; pid_t ret = waitpid(id, &status, 0); //阻塞等待 if(ret > 0) { printf("exit code:%d\n",WEXITSTATUS(status)); } } return 0; }
但是这里运行后发现,依旧不行,这里还是为空:
[wxq@VM-4-9-centos shell2]$ vim myshell.c [wxq@VM-4-9-centos shell2]$ make gcc -o myshell myshell.c [wxq@VM-4-9-centos shell2]$ ./myshell [dwr@VM-1-1-test shell]$ export MYVAL=1024 MYVAL=1024 putenv success [dwr@VM-1-1-test shell]$ ./test hello test! myenv: (null) exit code:0
这时候就有些丈二和尚摸不着头脑,明明设置成功了,为什么传递不行呢???
那就继续进行测试:打印一下父进程的环境变量
//export MYVAL=1024 if(strcmp(g_argv[0], "export") == 0 && g_argv[1] != NULL) { int ret = putenv(g_argv[1]); if(ret == 0) printf("%s putenv success \n", g_argv[1]); //debug int i = 0; for(i = 0; environ[i]; i++) { printf("environ[%d], %s\n", i, environ[i]); } continue; }
运行:
[wxq@VM-4-9-centos shell2]$ make gcc -o myshell myshell.c [wxq@VM-4-9-centos shell2]$ ./myshell [dwr@VM-1-1-test shell]$ export MYVAL=1024 MYVAL=1024 putenv success environ[0], XDG_SESSION_ID=459440 environ[1], HOSTNAME=VM-4-9-centos environ[2], TERM=xterm environ[3], SHELL=/bin/bash environ[4], HISTSIZE=3000 environ[5], SSH_CLIENT=122.193.66.176 16210 22 environ[6], SSH_TTY=/dev/pts/0 environ[7], USER=wxq environ[8], LD_LIBRARY_PATH=:/home/wxq/.VimForCpp/vim/bundle/YCM.so/el7.x86_64 environ[9], LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36: environ[10], MAIL=/var/spool/mail/wxq environ[11], PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/wxq/.local/bin:/home/wxq/bin environ[12], PWD=/home/wxq/study_4/shell2 environ[13], LANG=en_US.utf8 environ[14], SHLVL=1 environ[15], HOME=/home/wxq environ[16], LOGNAME=wxq environ[17], SSH_CONNECTION=122.193.66.176 16210 10.0.4.9 22 environ[18], LESSOPEN=||/usr/bin/lesspipe.sh %s environ[19], PROMPT_COMMAND=history -a; history -a; printf "\033]0;%s@%s:%s\007" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/~}" environ[20], XDG_RUNTIME_DIR=/run/user/1001 environ[21], HISTTIMEFORMAT=%F %T environ[22], _=./myshell environ[23], OLDPWD=/home/wxq/study_4 environ[24], MYVAL=1024 [dwr@VM-1-1-test shell]$
此时我们发现,这个环境变量确实已经存在于父进程的上下文当中(最后一行),但是为什么子进程接收不到呢???
对,我们再尝试一下,看看子进程是否接收到了这个环境变量:
//5.创建子进程进行程序替换 pid_t id = fork(); //child process if(id == 0) { printf("child process : MYVAL : %s\n", getenv("MYVAL")); //自定义环境变量 printf("child process : PATH : %s\n", getenv("PATH")); //系统环境变量 // execvp(g_argv[0],g_argv); execvpe(g_argv[0], g_argv, environ); exit(-1); }
运行测试:
[wxq@VM-4-9-centos shell2]$ ./myshell [dwr@VM-1-1-test shell]$ export MYVAL=1024 MYVAL=1024 putenv success [dwr@VM-1-1-test shell]$ ./test child process : MYVAL : (null) child process : PATH : /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/wxq/.local/bin:/home/wxq/bin hello test! myenv: (null) exit code:0 [dwr@VM-1-1-test shell]$
这里,系统的环境变量确实传递给了子进程,但是自定义的环境变量,父进程并没有传递给子进程,为什么???

可是为什么呢?明明这个环境变量存在于父进程的上下文当中啊。

上述问题,其实跟存储环境变量的位置有关:提示,环境变量是一个字符串!
- 当程序把我们自定义的环境变量添加到系统环境变量当中的时候
- 并不是把我们自定义的环境变量(字符串)拷贝到那个系统指针数组对应的空间里
- 而是把这个字符串的地址添加到系统的指针数组当中
- 另外虽然我们对子串进行了切割,但实际上我们的子串还是存储在cmd_line中
- 我们所有的字符串参数的地址,都是保留在g_argv内
- 因为所有的子字符串是写在了cmd_line里面,而每一次进入while循环,那么每一次都会进行覆盖
- 比如说g_argv[1]它里面指向的位置其实还是cmd_line,因为是从cmd_line中提取出来的
- 其实是把cmd_line里面的这个地址导入到了环境变量g_argv的表里面,而字符串还依旧在cmd_line内
- 所以在下一次进入while循环的时候,cmd_line已经被清空了(字符串被覆盖了),但是此时环境变量的地址没变,也就意味着环境变量的地址为空
分析这么多,我们来看看到底是不是因为这个问题:
测试:我们再设置一个buffer,拷贝这个字符串
//注意 main函数外声明 char buffer[32]; if(strcmp(g_argv[0], "export") == 0 && g_argv[1] != NULL) { strcpy(buffer,g_argv[1]); int ret = putenv(buffer); if(ret == 0) printf("%s putenv success \n", g_argv[1]); continue; }
接下来就是见证奇迹的时刻:
[wxq@VM-4-9-centos shell2]$ vim myshell.c [wxq@VM-4-9-centos shell2]$ make gcc -o myshell myshell.c [wxq@VM-4-9-centos shell2]$ ./myshell [dwr@VM-1-1-test shell]$ export MYVAL=1024 MYVAL=1024 putenv success [dwr@VM-1-1-test shell]$ ./test hello test! myenv: 1024 exit code:0 [dwr@VM-1-1-test shell]$
因为删除有些复杂,所以在这里并么有实现,但是可以按住键盘上的ctrl+删除,这样也能在脚本下完成删除,另外退出脚本ctrl+c就可以完成。
附上完整测试代码:
myshell.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h> //命令行解释器 //shell 运行原理:通过让子进程执行命令,父进程等待&&解析命令 //保存完整的命令行字符串 -- 充当缓冲区 #define NUM 1024 char cmd_line[NUM]; //保存切割之后的字符串 #define SIZE 32 char* g_argv[SIZE]; #define SEP " " extern char** environ; char buffer[32]; int main() { //0.命令行解释器,一定是一个常驻内存的进程 --- 不退出(死循环) while(1) { //1打印出提示信息 -- [wxq@VM-4-9-centos shell]$ printf("[dwr@VM-1-1-test shell]$ "); fflush(stdout); //2.获取用户的键盘输入[输入的是各种指令和选项"ls -a -l"] memset(cmd_line, 0, sizeof cmd_line); if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL) { continue; } //输入的回车键设为\0 ls -a -l \n \0 cmd_line[strlen(cmd_line)-1] = '\0'; // printf("echo:%s\n", cmd_line); //debug //3.命令行字符串进行解析 "ls -a -l -s" ---> "la" "-a" "-l" "-s" g_argv[0] = strtok(cmd_line, SEP); //第一次调用,传入原始的字符串 int index = 1; while(g_argv[index++] = strtok(NULL, SEP)); //第二次调用,如果还要分割原始字符串,传入NULL //export MYVAL=1024 if(strcmp(g_argv[0], "export") == 0 && g_argv[1] != NULL) { strcpy(buffer,g_argv[1]); int ret = putenv(buffer); if(ret == 0) printf("%s putenv success \n", g_argv[1]); //debug // int i = 0; // for(i = 0; environ[i]; i++) // { // printf("environ[%d], %s\n", i, environ[i]); // } continue; } //简单配置ls的颜色 int i = 1; if(strcmp(g_argv[0], "ls") == 0) { g_argv[i++] = "--color=auto"; } //识别别名 - 主要是测试,一般是有接口的 if(strcmp(g_argv[0], "ll") == 0) { g_argv[0] = "ls"; g_argv[i++] = "-l"; g_argv[i++] = "--color=auto"; } //debug // for(index = 0 ; g_argv[index]; index++) // { // printf("g_argv[%d]: %s\n", index, g_argv[index]); // } //4.TODO 内置命令:让父进程(shell)自己执行的命令。我们叫做内置命令,内建命令 //内建命令本质就是shell中的一个函数调用 if(strcmp(g_argv[0], "cd") == 0) //not child execute; father execute; { if(g_argv[1] != NULL) { chdir(g_argv[1]); //cd cd .. } continue; } //5.创建子进程进行程序替换 pid_t id = fork(); //child process if(id == 0) { // printf("child process : MYVAL : %s\n", getenv("MYVAL")); //自定义环境变量 // printf("child process : PATH : %s\n", getenv("PATH")); //系统环境变量 // execvp(g_argv[0],g_argv); execvpe(g_argv[0], g_argv, environ); exit(-1); } //father int status = 0; pid_t ret = waitpid(id, &status, 0); //阻塞等待 if(ret > 0) { printf("exit code:%d\n",WEXITSTATUS(status)); } } return 0; }
- 所以排查这个问题,需要明白
- 导入环境变量实际上是在指针数组中添加指针的
- 而我们在命令行添加环境变量的本质,实际上是
- 调用putenv添加到了当前的父进程(shell的上下文)当中,我们不能手动添加到数组里,必须要使用这样的接口
- 添加了之后,(其实是调用接口添加到了虚拟地址空间) --- 栈区再往上的空间就是环境变量空间
- 接下来创建的子进程都会默认的去添加这个环境变量,因为子进程继承了父进程的地址空间,当然也就继承了环境变量
- 所以环境变量具有全局属性
但是这里存在一个问题,是关于:创建子进程,进行程序替换的时候,代码和数据都存在写时拷贝吗???
参考:程序地址空间
这里需要捋顺一个问题:
首先:
父进程创建子进程,如果父进程或者子进程对数据进行写入,那么操作系统就会发生写时拷贝。
此时代码不会进行写时拷贝,代码是不可更改的,因为代码存储在常量区,父进程或者子进程只有读取的权限
(代码共享,数据写时拷贝。父子进程共用虚拟地址空间)
其次:
如果子进程发生替换程序,意味着新程序会从磁盘往内存中加载,这不就是写入吗?
此时,代码会不会发生写实拷贝,答案是肯定,因为要保证进程的独立性,此时,父子的代码必须要分离
(代码,数据写时拷贝,重新给子进程建立虚拟地址空间)
再次:
那么,回到上面的问题,环境变量具有全局属性,但是程序替换不是会替换掉所有的代码和数据吗?
为什么子进程还可以使用父进程的环境变量???
因为环境变量是一个特例,可以理解为特殊情况,在进行程序替换的时候,不会被替换掉
或者可以理解为,在给子进程创建虚拟地址空间的时候,会拷贝一份给子进程。
那就再多说一句吧,为什么定义环境变量在其他的程序中也能使用?
因为export定义环境变量是会定义给bash,而bash就是shell外壳,也是所有程序(进程)的父进程,所以定义的环境变量就具有了全局属性

280

被折叠的 条评论
为什么被折叠?



