Linux-命令行参数和环境变量 摘要本文深入讲解Linux环境变量与命令行参数的核心机制。文章从main函数的参数形式入手详细剖析了argc与argv的含义、命令行参数如何被父进程bash打散并传递给子进程以及如何通过命令行参数实现程序的不同功能。随后系统介绍了环境变量的基本概念重点讲解了PATH环境变量的作用与配置方法并通过实验验证了操作系统在PATH中查找可执行程序的机制。文章还介绍了env、getenv、environ等多种获取环境变量的方法解释了环境变量表的形成过程及其内存级特性并通过本地变量与环境变量的对比阐明了环境变量具有全局属性、可被子进程继承的根本原因。目录1.命令行参数2.环境变量2.1历史经历2.2环境变量基本概念2.3Linux当中的环境变量2.4其他的环境变量2.5如何多方法获取环境变量2.6为什么要有环境变量2.7环境变量的特点和总结1.命令行参数main函数可以有参数吗可以有几个参数啊是哪几个我们首先创建一个testArg.c然后写下如下代码#includestdio.h int main() { return 0; }学过C语言的都知道这个是最基本的main函数它可以成功运行但是我们可能见过如下的main函数#includestdio.h int main(void) { printf(hello\n); return 0; }这个编译后也能成功运行有些教材上也会这样写main函数#includestdio.h int main(int argc,char *argv[]) { printf(hello\n); return 0; }如果我们编译运行也是能运行成功的那么char *argv[]我们知道是一个指针数组char*类型要么指向字符要么指向字符串。实际上argv是指向一个一个字符串的指针数组数组有多少个元素就是int argcarg即argument参数c即count数量argv即argument vector即参数的指针数组argc是argv数组元素的个数。那么argv指向的到底是谁呢我们可以把代码改为这样#includestdio.h int main(int argc,char *argv[]) { int i 0; for(;iargc;i) { printf(argv[%d]: %s\n,i,argv[i]); } //printf(hello\n); return 0; }编译运行后有那么我们把指令改为./a.out -a有把指令改为./a.out -a -b有我们可以这样理解其实我们在命令行输入的东西其实都是字符串./a.out是字符串-a是字符串。而命令行当中的指令被打散成n个字符串n是空格数1并且把每个字符串放在argv中对应的位置。①为什么这么做呢②谁来做呢③怎么做呢回答①如果把代码改为#includestdio.h int main(int argc,char *argv[]) { if(argc ! 2) { printf(Usage: %s -v1/-v2/-v3\n,argv[0]); return 1; } //int i 0; //for(;iargc;i) //{ // printf(argv[%d]: %s\n,i,argv[i]); //} //printf(hello\n); return 0; }这个代码的意思是当我们输入指令执行代码时必须要带一个选项类似./a.out -a一样否则提示错误编译运行有如下结果如果我们把代码改为#includestdio.h #includestring.h int main(int argc,char *argv[]) { if(argc ! 2) { printf(Usage: %s -v1/-v2/-v3\n,argv[0]); return 1; } if(strcmp(argv[1],-v1) 0) printf(这是功能1\n); else if(strcmp(argv[1],-v2) 0) printf(这是功能2\n); else if(strcmp(argv[1],-v3) 0) printf(这是功能3\n); else printf(功能不支持\n); //int i 0; //for(;iargc;i) //{ // printf(argv[%d]: %s\n,i,argv[i]); //} //printf(hello\n); return 0; }这就相当于argv[0]指向我们的可执行程序-v1/-v2/-v3是三个选项但是我们只准用其中有且一个选项。此时我们把a.out重命名为myls有如果我们myls myls -v1 ,myls -v2,myls -v3,myls -t有那么命令行参数的意义是什么我们在Linux用到的大部分指令绝大部分都是C语言写的如果我们ls ls -l ls -l -a可以分别以下面三种形式呈现以ls为代表的各种命令本质是程序用C语言写的而-l-a我们当时把它们叫做选项今天我们把选项视作字符串选项本质是字符串可以以一定的方式传递给你ls内部的“main”在ls内部实现的时候就可以根据不同的选项实现类似功能的不同表现形式。通俗来讲原来我们学的这些命令带的选项本质上就是通过命令行参数实现的命令行参数命令选项②谁来做呢当我们输入ls -l -a这些字符串在没敲回车之前这些东西并没有被当做一个进程是被父进程bash先拿到的在命令行当中启用的所有进程的父进程都是bash父进程的数据子进程能看到只要它不修改那么fork创建子进程之后代码和数据都是共享的因此子进程就把数据从父进程继承下来子进程就拿到了命令行参数数据了。但是谁给子进程拿到的命令行参数呢这个需要到后面再讲解。③问题要回答需要等到讲解到程序替换章节才行结论①命令行参数至少有一个②进程对应的程序名字一般是argv[0]③有几个子串argc就是几空格隔开。如果我们把代码改为#includestdio.h #includestring.h int main(int argc,char *argv[]) { if(argc ! 2) { printf(Usage: %s -v1/-v2/-v3\n,argv[0]); return 1; } printf(argc %d\n,argc); if(strcmp(argv[1],-v1) 0) printf(这是功能1\n); else if(strcmp(argv[1],-v2) 0) printf(这是功能2\n); else if(strcmp(argv[1],-v3) 0) printf(这是功能3\n); else printf(功能不支持\n); //int i 0; //for(;iargc;i) //{ // printf(argv[%d]: %s\n,i,argv[i]); //} //printf(hello\n); return 0; }然后我们测试多个参数或一个参数时的结果命令行参数在被父进程bash获取时通常被维护成数组的样子即argv它是一个指针数组然后这个数组的每个参数会指向你在命令行当中输入对应的选项如argv[0] ./a.out等当我们在命令行中输入对应的命令行参数的时候bash就会把命令行参数打散变成指针数组依次指向一个一个子串./a.out)(-v1)有效的元素个数为这个数组的有效元素个数父进程bash把这些命令行参数维护成表格的样子然后再传递给argc和argv我们就能拿到了。如果我们没有在main函数带这些参数那么就不会传递其中argv数组必须在我们设置完有效的字符串之后最后一个元素必须以NULL结尾我们把代码改为#includestdio.h #includestring.h int main(int argc,char *argv[]) { //if(argc ! 2) //{ // printf(Usage: %s -v1/-v2/-v3\n,argv[0]); // return 1; //} //printf(argc %d\n,argc); //if(strcmp(argv[1],-v1) 0) // printf(这是功能1\n); //else if(strcmp(argv[1],-v2) 0) // printf(这是功能2\n); //else if(strcmp(argv[1],-v3) 0) // printf(这是功能3\n); //else // printf(功能不支持\n); int i 0; for(;argv[i];i) { printf(argv[%d]: %s\n,i,argv[i]); } //printf(hello\n); return 0; }此时也能正常执行NULL就是0运行到无效字符串时就会执行结束argv叫命令行参数它的本质可以让我们的程序获取将来我们输入的一个一个的选项可以让我们在程序内部对选项进行判定 判定之后就能让我们的程序呈现不同的功能2.环境变量2.1历史经历如果我们直接讲环境变量大概率是听不懂的因此我们需要先谈一下历史经历我们在电脑搜索框输入环境变量有我们点击编辑系统环境变量后有点击右下角的“环境变量N”有其中上面一部分是用户的环境变量下面一部分是系统环境变量。有时候我们安装软件会询问我们是否将我们的软件添加到系统的path中这在安装某些如Python、Java这类编译器的时候会遇到。当时我们并不清楚为什么要这样做但基本上我们也要这么做。因为如果我们不把环境变量添加到系统里某些程序运行不了或者在命令行里面运行不了。往往我们安装软件的时候改的参数是系统级别的环境变量上图的Path然后在里面点击Path按编辑添加一种路径添加路径完后某些程序就可以直接运行2.2环境变量基本概念定义环境变量是系统级别的一些全局变量 这些全局变量通常具备不同的用途这种变量我们叫做环境变量。但是这样我们确实不怎么理解这个环境变量是什么环境变量的基本概念①环境变量(environment variables)⼀般是指在操作系统中⽤来指定操作系统运⾏环境的⼀些参数②如我们在编写C/C代码的时候在链接的时候从来不知道我们的所链接的动态静态库在哪⾥但是照样可以链接成功⽣成可执⾏程序原因就是有相关环境变量帮助编译器进⾏查找。③环境变量通常具有某些特殊⽤途还有在系统当中通常具有全局特性2.3Linux当中的环境变量在Linux中的环境变量最常见的是PATH。我们会发现一个问题如果我们直接使用ls是可以的但是执行我们的可执行程序却需要带./为什么ls不用带./呢即为什么ls不用带路径./a.out相当于绝对路径了为什么执行我们自己的可执行程序必须带路径呢a.out:command not found提示这个报错是因为操作系统要找我要执行的可执行程序但没找到命令不存在那么操作系统到底是在哪里找我们的指令的呢我们的系统命令在/usr/bin路径下如果我们ls /usr/bin/ls是能找到ls命令的在/usr/bin目录下是存在我们的命令的所以系统会自动在/usr/bin路径下找命令。但是为什么系统会在/usr/bin路径下去找而不去其他路径下找呢这是因为/usr/bin路径在系统变量PATH中存在的即PATH包含了这个路径。操作系统查找可执行程序是在PATH环境变量里去找的操作系统查找可执行命令是在环境变量PATH中查找的所以Windows里也有一个Path点开它会有对于类似D:\ProgramFiles\Xshell8这种路径在该路径下放着一系列的可执行程序。如果我们到文件中查找该路径能看到所以为什么我们安装软件时要把对应软件的路径添加到Path里因为我们将来想在命令行当中直接执行这个命令不用带路径如果各位有安装了Python的可以直接在命令提示符中输入python就能打开python。Windows下的环境变量Path和Linux下的PATH这两个工作是一样的都是直接从Path中直接查找对应的命令是否有效。如果我们此时输入python一般会让我们直接下载python第二步会有如果输入y就会把python的路径添加到Windows的Path中此时在命令提示符中输入python就能成功运行了如果我们输入print(hello)能够成功执行并且我们可以在用户或系统级别的环境变量看到这个双击箭头所指那么怎么证明操作系统查找可执行命令是在环境变量PATH中查找的如果我们想要查看环境变量的内容在Xshell输入以下指令echo PATH结果为这是因为系统自动把PATH认定为字符串了如果我们想查看环境变量的内容可以用echo $PATH此时就能成功看到至此我们就能看到PATH所对应的一个一个的子路径用:分割的就是一个子路径也就是说如果我们输入ls它会先在/usr/local/sbin路径下查找有没有ls命令如果没有再在/usr/local/bin路径下查找直到查找到/usr/bin路径下发现有这个命令如果都没有在子路径找到那么就会报错command not found所以我们直接a.out是并不能运行的这是因为我们的a.out并没有在PATH中任何一个子路径下所以找不到。①那么如果我们把PATH对应的路径都清空了那是不是代表绝大多数指令都执行不了了呢怎么改环境变量的值呢PATH支持赋值即PATH类似我们语言中的赋值语句如果想改为空直接PATH 注意这个指令等号左右不能带空格否则会有然后运行刚刚指令有此时我们执行很多指令都失败了可能有些指令还是成功的这就证明了系统搜索命令是在PATH中搜索的我们把这些路径弄没了就跑不了这些路径下的指令除非带路径。那么我们的系统是不是没了我们把Xshell关闭了重新打开重新登录此时我们就能够正常执行ls 就算echo $PATH也能正常执行对比一下我们刚刚的那个路径很容易发现这个PATH环境变量中的内容没有变化不管是云服务器还是虚拟机都没关系如果实在怕恢复不了可以先把环境变量得到之后复制到剪贴板然后到时候再用PATH赋值该路径即可如PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin我们先不要管这个环境变量是从哪里来的怎么恢复的到时候会说②我能不能把我自己的绝对路径添加到环境变量里将来执行我们的可执行程序的时候就不用带路径了呢如果想要导入新的环境变量用export PATH$PATH:你想要添加的路径因为PATH 是赋值不能直接把我们的路径全部粘贴上去应当保留之前的环境变量才行。我们先把我们想要添加的路径复制下来然后替换你想要添加的路径这里即可类似export PATH$PATH:/home/tan/8.8/the_main然后我们可以用echo $PATH看结果此时我们就成功把该路径添加进去了可以直接使用a.out随便在哪个路径下都可以执行此时我们执行我们的程序就和执行系统命令完全一样了因此我们在安装软件时添加环境变量因为我们如果不添加我们安装的可执行程序默认只能在安装路径下执行为了让Python和Java能在任意地方随时执行所以需要把安装好的软件的可执行程序路径添加到环境变量里。那么这个PATH和当前用户是谁和当前所在路径有关吗是不是我在任意地方都能执行我要执行的命令呢这个PATH是系统定义的全局变量我们在任何地方都可以被bash找到然后帮我们搜索路径而PATH这个环境变量的主要功能是协助我们执行可执行程序查找的全局变量是什么需要我们到时候讲解本地变量和局部变量的时候才能讲解我们之前还学过which命令可以which 指令 来查找指令所在的路径which也是在根据PATH进行查找2.4其他的环境变量在Linux系统中环境变量有很多除了PATH以外还有其他的环境变量如何查看这些环境变量呢使用env可以查到我当前系统在当前用户下所对应的所有环境变量env即environment的简写有些云服务器可能有HOSTNAME这个代表主机名称TERM当前所采用的终端类型xterm远程登录如果是虚拟机登录的VMware上有SHELL我们当前所采用的SHELL叫做/bin/bashbash也是一个命令只不过它运行后就是一个死循环它会帮我们从命令行获取和解析信息HISTSIZE我们可以通过键盘上的↑↓来查找历史命令而且还可以通过Ctrlr搜索历史命令这就决定了你所敲的命令在Linux系统中是会把我们所敲的历史命令记录下来而记录下来的命令条数总不能一直记录所以记录的指令个数一共为HISTSIZE个如果HISTSIZE1000代表记录1000条历史命令可以通过history查看历史命令一共1000行我只展示最后几行粘贴不了那么长的图片:但是我们发现有1479条命令但我们如果使用history | wc -l能查看history的指令条数为因为它的编号并不是从1开始除非你敲的指令少于1000行SSH_CLIENT代表当前IP地址登录号等信息OLDPWD:如果我们pwd为在cd指令中我们学到过一个cd -这个指令可以进入最近一次的目录如果我们cd -有我们就进入到之前的路径了此时我们再env有发现OLDPWD是我们之前所在的路径如果cd - 有如果我们:echo $OLDPWD有也就是说我们cd -的时候cd是通过OLDPWD来知道最近一次所处的目录因为环境变量OLDPWD把最近的路径记录了下来我们的cd -会被bash解释成:cd $OLDPWDSSH_TTY这代表当前用户所关联的终端文件是谁如果我们再起一个终端那么env有这个SSH_TTY就算显示器文件如果我们在SSH_TTY为/dev/pts/1的机器上输入echo aaaa/dev/pts/0那么就会有我们对应的SSH_TTY代表的是和当前用户相关联的终端文件是谁。USER代表当前我们正在访问Linux系统所对应的用户是谁即我们可以通过whoami指令的结果LS_COLORS就是我们输入ls的时候它会给我们带颜色LS_COLORS代表ls输出时的颜色输出方案。PWDpwd是通过PWD环境变量来知道当前所处的路径我们可以用echo $PWD能实现和pwd一样的效果且PWD自动更新PWD会把当前用户所在的路径记录下来因此我们可以很容易得到当前路径之后我们如果想要做一个自己写的pwd我们只要想办法获取系统里的环境变量就行LANG表示当前语言UTF-8代表支持汉字。LOGNAME当前登录用户是谁。HOMELinux系统有自己的家目录也就是说我们把Xshell关闭重新登录某个用户默认所处的路径就叫做家目录HOME叫做当前用户的家目录我们的家目录为什么为/home/tan也是由环境变量决定root账号的PWD叫做/root这里就不演示了。换句话说环境变量本身在我们进行登录系统时被天然的优先初始化然后形成环境变量来支撑我们进行各种操作而这种环境变量我们可以在任何地方随时访问这种变量我们叫做系统级别的全局变量不同的变量具备不同的用途2.5如何多方法获取环境变量main函数可以有几个参数在我们之前的命令行参数中main函数可以接受一个void类型的参数、int类型的argc和char*类型的argv共三个参数但是实际上main函数还可以这样写#includestdio.h #includestring.h int main(int argc,char* argv[],char* env[]) { int i 0; for(;argv[i];i) { printf(argv[%d]:%s\n,i,argv[i]); } return 0; }也就是说main函数可以有第三个参数env实际上是一个指针数组它的指针指向一个一个的字符串而这个字符串就算环境变量KV的字符串。也就是说我们刚刚使用env看到的所有环境变量本质①等号左边是K等号右边是V红色区域为key蓝色区域为value类似我们在C学到过的pair键值对。本质②env的结果每一行被当做一行字符串。我们的系统会把环境变量维护成指针数组的样子然后把环境变量的地址依次填写进char* env[]里因此我们把env指针数组称为环境变量表且这个环境变量表也必须以NULL结尾。如果要验证可以把代码改为#includestdio.h #includestring.h int main(int argc,char* argv[],char* env[]) { int n 0; for(;env[n];n) { printf(env[%d]:%s\n,n,env[n]); } //int i 0; //for(;argv[i];i) //{ // printf(argv[%d]:%s\n,i,argv[i]); //} return 0; }有如下的结果因此我们可以让我们的C/C程序获取系统的环境变量了所以获取环境变量方法①main函数获取。如果我们可以获取环境变量可以使用getenv函数我们使用man getenv看getenv具体功能翻译一下就是方法②通过函数getenv获取单个环境变量。char *getenv(const char *name)我们需要传入我们想要获取的环境变量名字拿key获取等号左边获取。实际上getenv就是遍历env这个表然后通过你传入的参数来匹配每一行等号左边的字符串如果找到了就把对应等号右边的字符串提取出来如果没找到返回为空如果找到了如果找到了返回指针对应的value如#includestdio.h #includestring.h #includestdlib.h int main() { char* path getenv(PATH); printf(PATH %s\n,path); //int n 0; //for(;env[n];n) //{ // printf(env[%d]:%s\n,n,env[n]); //} //int i 0; //for(;argv[i];i) //{ // printf(argv[%d]:%s\n,i,argv[i]); //} return 0; }然后我们运行有如果我们打印PATH里面的内容有发现完全一样此外我们还可以使用environ这个全局变量来获取环境变量可以用man 7 environ看具体内容其实environ就是一个全局的变量包含于头文件unistd.h但是后面优化了一下就看不到头文件这种了。方法③使用全局指针来获取环境变量实际上这个全局指针不用初始化也可以不包含头文件我们可以直接通过这个全局指针来获取所有的环境变量environ是一个耳机指针指向环境变量表#includestdio.h #includestring.h #includestdlib.h int main() { extern char **environ; int i 0; for(; environ[i]; i) { printf(environ[%d]:%s\n,i,environ[i]); } //char* path getenv(PATH); //printf(PATH %s\n,path); //int n 0; //for(;env[n];n) //{ // printf(env[%d]:%s\n,n,env[n]); //} //int i 0; //for(;argv[i];i) //{ // printf(argv[%d]:%s\n,i,argv[i]); //} return 0; }运行有那么进程是如何获取环境变量的不是进程自己获得了环境变量是进程的父进程bash获得了环境变量父进程不仅仅获得了环境变量还形成了环境变量表环境变量表是数据吗是父进程的数据吗我们之前讲过我们命令行中所启动的子进程会继承父进程的代码和数据当不修改数据时数据能被子进程看到因此能用上述三种方法获得父进程的环境变量表父进程bash在进行数据解析时会形成两张表一张表叫做命令行参数表argv一张表叫做环境变量表env。其中命令行参数表一直在变而环境变量表相对比较稳定。那么bash又是从哪里拿到的环境变量呢它是从系统配置文件中获取到的系统配置文件会记录相关环境变量的内容当bash就会读取系统配置文件然后创建一个指针数组指向这些配置文件的内容创建出了一个环境变量表。也就是说环境变量表是内存级的表因此我们的PATH在清除后为什么重新启动XShell又能重新恢复了的原因是我们清除的PATH是内存级别的当重新登录时bash会重新从配置文件里读取环境变量包含PATH。配置文件在哪里它在我们当前任意一个用户的家目录下的隐藏文件里如果我们切换到家目录并且ls -al显示所有文件包含隐藏文件我们需要注意隐藏文件我们只需要知道配置文件在哪里即可具体内容我们看了也看不懂。2.6为什么要有环境变量如果我想写一个只有自己能执行的可执行程序呢root都不能执行我们写下如下代码#includestdio.h int main() { printf(我是一个只有我自己能够执行的可执行程序!\n); return 0; }这是最初始的代码如何保证自己能够执行呢我们通过getenv得到USER只有tan才能登录因此写下如下代码#includestdio.h #includestdlib.h #includestring.h int main() { char* who getenv(USER); if(strcmp(who,tan)) { printf(我不让你执行:%s\n,who); return 1; } printf(我是一个只有我自己能够执行的可执行程序!\n); return 0; }系统会检测当前登录用户只有tan用户才有权利执行该文件如果登录时是用tan但是用su切换到其他用户其USER还是tan其他用户登录时还是可以执行因此环境变量为什么要有答案是不同的环境变量会有不同的用途如果我们自己想写一个自己的pwd可以把代码改为#includestdio.h #includestdlib.h #includestring.h int main() { printf(%s\n,getenv(PWD)); //char* who getenv(USER); //if(strcmp(who,tan)) //{ // printf(我不让你执行:%s\n,who); // return 1; //} //printf(我是一个只有我自己能够执行的可执行程序!\n); return 0; }此时就能用./self执行pwd命令了路径不管怎么改都还是能成功获取pwd是什么吗进程会记录下来当前的工作路径称为cwd ------ 当前路径cwd所以我们新建文件或删除文件时它会默认再当前路径下新建/删除文件----- 父进程bash有没有cwd -------- 所有进程都有cwd父进程bash的cwd在bash进程自己的task_struct内部保存 --------- 创建子进程以父进程task_struct为模板创建。自己的pwd从哪里来 ------- 从bash来。所以cwd是操作系统内核的东西因此操作系统会给我们系统调用来获取当前的cwd用getcwd获取翻译过来是其中两个参数buf,sizebuf为缓冲区传递这个缓冲区会把当前工作路径打印出来size为缓冲区的大小。演示代码#includestdio.h #includestdlib.h #includestring.h #includeunistd.h int main() { char pwdbuf[128]; char* pwd getcwd(pwdbuf,sizeof(pwdbuf)); printf(%s\n,pwd); //char* who getenv(USER); //if(strcmp(who,tan)) //{ // printf(我不让你执行:%s\n,who); // return 1; //} //printf(我是一个只有我自己能够执行的可执行程序!\n); return 0; }此时运行有因此也能获得当前工作路径进程对应的工作路径。bash的pwd从哪里来bash在底层调用getcwd然后更新环境变量bash从getcwd获得当前的工作路径后更新环境变量表中的PWD此时就能被子进程看到了修正概念环境变量有相当一部分大部分是从配置文件中来还有一部分是动态获取的即环境变量大部分是从配置文件中来也有少部分是启动之后动态获取或者创建的2.7环境变量的特点和总结环境变量为什么有全局属性在Linux的命令行上我们可以在上面定义变量如key100然后echo $key就能看到key的值但这种变量并没有在全局变量当中环境变量中这个key变量只在本shell中有效即我们在环境变量表中并不能看到key这个环境变量。如果想要把key变成环境变量输入export key此时就能查到了如果想要把key取消掉全局变量属性输入unset key如果我们这样写export key1234也能看的到key在环境变量里我们把最开始key100没有在环境变量里的变量叫做本地变量在命令行中不加export本地变量只在bash内部有效。bash并不会把key添加到环境变量表里只有环境变量表才能被子进程继承。如何查本地变量set它会把所有的本地变量包含环境变量的所有变量都打印出来不过这样的方式会刷屏本地变量我们未来大概率不会使用提出来是为了对比如果我们输入以下代码#includestdio.h #includestdlib.h #includestring.h #includeunistd.h int main() { char* env getenv(MYENV); if(env NULL) printf(MYENV不存在!\n); else printf(%s\n,env); //char pwdbuf[128]; //char* pwd getcwd(pwdbuf,sizeof(pwdbuf)); //printf(%s\n,pwd); //char* who getenv(USER); //if(strcmp(who,tan)) //{ // printf(我不让你执行:%s\n,who); // return 1; //} //printf(我是一个只有我自己能够执行的可执行程序!\n); return 0; }如果直接执行结果一定为如果我们定义一下MYENV的本地变量然后再执行此时还是MYENV不存在 因为./self本质是创建了一个子进程而./self拿不到bash创建的本地变量但是如果:export MYENV此时再运行就能了我们知道./self是子进程环境变量会被子进程继承下去本地变量不会被子进程继承孙子进程曾孙子进程也能拿到环境变量环境变量一旦建立好所有的子进程都能拿到因此环境变量有全局属性系统级的全局变量那么echo $MYENV为什么当MYENV为本地变量的时候它仍然能正常输出结果呢echo是命令吗要不要创建子进程echo自己怎么获得的本地变量在Linux系统里大部分命令是真正存在的可执行程序需要通过创建子进程的方式来执行。但在Linux中有一部分命令执行的时候没有风险需要bash自己执行这种命令叫做内建命令也就是说echo在执行的时候相当于bash中的一个函数去执行了。但是我们which echo会有虽然它确实存在在一个文件里但是在命令行上写echo都会被bash解释成bash内部的函数不会创建子进程。至于为什么还存在一个echo在文件里Linux还有一种执行模式叫做以脚本的形式执行脚本模式执行时它需要真正存在一个echo命令不然执行会报错cd命令也不会创建子进程因为我们如果cdbash的PWD会变化如果是创建子进程的话那么cd对应的数据并不会传递到父进程里它只能继承父进程的数据但是不能给父进程传递数据因此cd指令也是一个内建命令。内建命令到自定义Shell再详细讲解