本篇将进入到 Linux 中的环境变量。首先我们将会联系 main 函数中的参数来讲解命令行参数,首先先讲解了 main 函数中的两个参数 argc 和 argv,接着介绍了本篇的核心——环境变量,详细的讲解了如何使执行程序的时候,不带路径的方法,介绍了获取环境变量的三种方法,同时得出了 main 函数中的第三个参数:env,最后介绍了 Linux 中的内建命令。最后介绍了 shell 程序中的内建命令。
目录如下:
目录
1. 命令行参数
我们先来探讨我们常用的 main 函数,通常我们的 main 函数都是不带参数的,但是我们可以给 main 函数带上参数吗,答案是可以的,main 函数的参数如下:
int main(int argc, char* argv[]) { return 0; }
那么关于这两个参数有什么意义呢?(argv 是一个指针数组,argc 则是这个指针数组中包含的元素个数)
我们通过如下代码来观察 argv 指针数组中包含的元素,如下:
1 #include <stdio.h> 2 #include <unistd.h> 3 4 int main(int argc, char* argv[]){ 5 int i = 0; 6 for(; i < argc; i++){ 7 printf("argv[%d]->%s\n",i ,argv[i]); 8 } 9 return 0; 10 }
运行结果如下:
通过如上的运行结果,我们可知:argv 指针数组可以获取我们命令行输入的字符串数组,并且还是一个变长数组,我们输入多少个字符串,就可以吸收多少个字符串。
那么为什么会这么做呢?如上的操作,运行一个程序,然后使用在运行程序后面加一个 -a / -b / -c / -d 指令,其实和其他我们常用的指令非常相似,比如 ls -l,ls -n,ls -a,所以命令行参数的本质是:交给我们程序不同的选项,用来定制不同的程序功能,命令行中会携带很多的选项。
1.1 bash的子进程
bash 作为 Linux 中的一个 shell 程序,我们输入的命令都需要由他进行解释。我们先通过如下代码,来观察一些现象:
1 #include <stdio.h> 2 #include <unistd.h> 3 4 const int g_val = 1000; 5 6 int main(){ 7 printf("I am a father process, pid: %d, ppid: %d, g_val: %d\n",getpid(), getppid(), g_val); 8 pid_t id = fork(); 9 if(id == 0){ 10 while(1){ 11 printf("I am a child process, pid: %d, ppid: %d, g_val: %d\n",getpid(), getppid(), g_val); 12 sleep(1); 13 } 14 }else{ 15 16 while(1){ 17 printf("I am a fathe process, pid: %d, ppid: %d, g_val: %d\n",getpid(), getppid(), g_val); 18 sleep(1); 19 } 20 } 21 return 0; 22 }
如上所示,当我们运行如上代码之后,子进程和父进程的 g_val 的值是一样的,说明父进程的数据,默认能被子进程看到并访问,也就是子进程可以继承父进程的数据。另一个现象是,每一次父进程的 ppid 都是2659,当我们查看的时候,发现2659为 bash 的pid。所以命令行中启动的进程,都是 bash 的子进程。
所以通过以上的连接,我们其实可以得出,我们在命令行中输入的命令行字符串,其实是被 bash 所读取,然后由 bash 把输入的字符串变为 agrv 中的参数,然后在传递给子进程(因为子进程可以继承父进程的数据),所以子进程可以根据得到的字符串,执行对应的功能。
2. 环境变量
平时我们在运行常用指令的时候,我们可以直接的运行我们的指令(或者加上一些运行参数),但是当我们运行我们自己写的程序的时候,我们就要在前面加上一个 ./ ,表示当前路径呢?
其主要原因是在 Linux 中,存在一些全局的设置,这些设置可以告诉命令行解释器(bash)去哪些路径下执行可执行程序(命令)。如下:
如上所示,PATH 就属于一种全局设置,这种设置在我们登陆 Linux 系统的时候,已经被加载到了 bash 进程中了(内存)。其实这种设置为环境变量,打印环境变量的内容:$PATH。最后我们获取到了 PATH 中的数据,为一串的路径,不同的路径之间使用冒号分隔开,当我们在命令行输入指令的时候,bash 就会到这些路径中去寻找指令,找到就加载运行,没找打就输出未找到。
2.1 不带路径运行程序
通过上文已经得知,我们运行某些指令不需要带路径,可以直接的运行指令,是因为环境变量中存在进程的路径,那么假若我们想要让我们自己写的进程运行时,不需要带路径,我们可以:
1. 将我们的程序拷贝到环境变量路径中;
2. 将此进程的路径加入到环境变量中。
如下:
如上所示,我们将我们的进程加入到环境变量的路径中,我们运行程序的时候,就不需要带路径运行自己写的程序了。(加入到环境变量的路径中需要 root 权限,通常不建议将自己写的程序写入到环境变量路径中,这里只是做演示)
如下:
当我们在 环境变量PATH后加入当前路径之后,当前路径运行的程序也不需要加路径了。注:当我们想要修改环境变量的时候,格式为:PATH=$PATH:新路径,不能直接对 PATH 进行赋值,因为会将 PATH 的原有路径给覆盖掉,导致原来的程序也不能运行了。
当我们想要将环境变量修改为初始状态的时候,我们只需要重新登陆我们的 Linux ,PATH 环境变量就变为初始状态了,说明对于环境变量来说,并不是存放在内存中的,而是存储在系统的对应配置文件中的,重新加载 Linux,就会重新加载对应的配置文件。
2.2 其他环境变量/导入环境变量
上文中我们只介绍了一个环境变量,在 Linux 中还存在其他环境变量,我们可以使用指令 env 查看所有环境变量:
如上所示,我们的 Linux 中存在很多的环境变量,在下文中我们将介绍几个常见的环境变量。
HOME:家目录,会记录每一个用户的家目录 PWD:记录当前的目录路径 SHELL:记录启动时需要使用的shell程序 HISTSIZE:记录近1000条历史命令 USER:当前使用的用户
那么我们如何导入自己的环境变量呢?使用命令:export NAME=value,如下:
但是这样的环境变量仅仅只在这次登陆中管用,下一次登陆 Linux 的时候,这个环境变量就不管用了,因为只是将这个环境变量写入到 bash 中,并不是写入到配置文件中,若想要永久生效,可以将其写入到配置文件中。若我们想要取消环境变量,只需要使用指令:unset NAME 即可。
2.3 在代码中获取环境变量
上文中讲了这么多环境变量,但都是在指令行获取,我们如何在代码中获取到我们的环境变量呢?如下代码:
1 #include <stdio.h> 2 #include <unistd.h> 3 4 int main(){ 5 extern char** environ; 6 int i = 0; 7 for(; environ[i]; i++){ 8 printf("env[%d]->%s\n", i, environ[i]); 9 } 10 return 0; 11 }
如上所示,通过代码我们也可以获取到我们的环境变量,而我们写出的可执行文件是 bash 的子进程,上文中也提到,子进程可以获取到父进程的数据,说明我们的环境变量是在 bash 内部的。
那么关于环境变量是如何在 bash 中组织起来的呢?在 bash 中会维护一个名为 env 的表,里面的每个元素的类型都是 char*,也就是对应的环境变量,末尾以 NULL 结尾,若想要加入一个环境变量,就是在这个表的末尾加入。在结合上文的命令行参数的内容,我们得出,bash 会默认给子进程形成两张表:argv[](命令行参数),env[](环境变量表),bash 可以通过各种方式交给子进程。那么和以上类似,在 main 函数的参数中,不仅仅可以有 argc argv,还可以有 env,如下:
所以,在 main 中可以存在三个参数。
2.4 获取环境变量的方式
在上文中已经提到我们可以使用 main 函数中参数 env ,也可以使用 extern char** environ 获取环境变量,我们获取环境变量的方式还有一种,通过函数 getenv("NAME"),如下:
2.5 本地变量
在 Linux 中不仅仅有环境变量,还存在本地变量,本地变量和环境变量非常相似,但是本地变量和环境变量的区别在于:环境变量具有全局属性,而本地变量只具有局部变量。如下:
我们设置了一个环境变量 HOOO,然后使用 echo 命令确实可以将其打印出来,但是当我们在 env 中查找时,却找不到,说明这样设置的一个变量其实是一个本地变量,并没有被放入到 env 表中。(本地变量只在本 bash 中有效,无法被子进程继承,只有将本地变量转化为环境变量,才可以被子进程继承)
当我们想要将一个本地变量变成环境变量的时候,我们只需要使用 export 修饰一下该本地变量,就可以了。如下:
3. 内建命令
在上文中我们提到使用 export NAME 指令可以增加环境变量,同时我们也提到环境变量是在 bash 内部的,那么 export NAME 命令作为作为一个指令,一个有 bash 生成的子进程,子进程写入的数据,怎么能被父进程发现呢?上文中只提到子进程可以继承父进程的数据,并没有提到父进程可以继承子进程的数据。
其实在 Linux 中,大概有 80% 的命令都是 bash 生成子进程进行执行的,剩下的 20% 是由 bash 亲自执行,也就是内建命令(可以将内建命令理解为 bash 中的函数),这种命令即使修改了换件变量也还可以使用,除了 export,还有 echo 指令如下:
如上所示,我们先将环境变量 PATH 置空,然后在运行指令 export echo 都可以运行,当我们运行指令 ls mkdir 时却不可以运行。