你是不是被以下的问题困扰,还不能真正看懂?什么是export,什么时候用export,为什么有时用export和source?为什么要用env设置环境变量而不用export,有什么好处?source和exec有什么区别?本文试图通过普及unix进程、环境变量等概念,让读者真正了解这些shell命令的本质,了解这些命令的使用场合。首先,我们先来解释一下这些命令。如果读者能够完全理解,那么这篇文章可能对你帮助不大。set设置当前shell进程的局部变量,局部变量只在当前shell进程内有效,不会被子进程继承和传递。env仅为将要执行的子进程设置环境变量。export将一个shell局部变量提升为当前shell进程的环境变量,使其自动被子进程继承,但导出的变量不能改变父进程的环境变量。source运行脚本时,并不会启动一个新的shell进程,而是在当前shell进程的环境中运行脚本。exec运行一个脚本或命令时,不会启动一个新的shell进程,exec后面的脚本内容也不会执行,即当前shell进程结束。在这些表述中,反复提到了进程和环境变量的概念。要想深入理解它的含义,还必须理解进程的相关概念。进程和环境变量进程是程序执行上下文的集合,包括程序代码、数据段、堆栈、环境变量和内核用来标识进程的数据结构。一个进程可以生成另一个进程,生成的进程称为子进程,那么对应的还有一个父进程,即所谓无穷无尽的子孙。子进程会继承父进程的一些遗传因素,包括本文的主题环境变量。环境变量是一组特殊字符变量。由于具有继承性,环境变量常被用来在父子进程之间传递参数,这一点在shell编程中尤为突出。fork和exec通过在Unix系统中依次调用fork()和exec()系统调用来创建子进程。Fork实际上是克隆。githubfork别人的项目为什么叫fork?事情就是这样发生的。所谓“克隆”就是将当前进程的内存映像全部复制到内存中。一切都一样,只是修改了新进程的进程ID(PID)。与细胞分裂有些相似,细胞分裂后产生的细胞与原始细胞具有完全相同的遗传因子。因为fork()会复制整个进程,包括该进程运行到哪些代码,也就是说新进程会继续执行fork()之后的代码,父进程也会运行fork()之后的代码,开始从fork()父子进程分道扬镳。如果fork返回>0,说明在父进程中,如果fork返回==0,说明在子进程中:pid=fork();if(pid==0){//在子进程中childprocess}elseif(pid>0){//Parentprocess}准确的说exec是一组函数的统称,exec的确切定义是替换textfield,datasegment,stacksegment当前进程与磁盘上的新程序。所以exec并没有创建一个新的进程,而是替换了它。这样,进程就会从新代码的main开始执行,相当于运行了一个完全不同的程序,但是保留了原来的环境变量。根据本文的主题,exec函数可以分为两类,一类可以设置并传递新的环境变量,一类不能传递新的环境变量,只能继承原有的环境变量。换句话说,当运行一个新程序时,有机会改变新程序的环境变量,而不仅仅是继承。如以下变体,可以通过envp参数设置环境变量intexecve(constchar*filename,char*constargv[],char*constenvp[]);作为父进程,可以通过waitpid()函数等待子进程退出,并获取退出状态。一个进程可以通过setenv或者putenv改变自己的环境变量,但是环境变量的继承只能是单向的,即从父进程继承到forked子进程。即使子进程修改了自己的环境变量,也无法改变父进程的环境变量。shellshell没什么特别的,它也是一个进程,当我们在命令行输入命令回车后,shell进程会通过fork和exec为我们创建一个子进程(有一小部分命令是这样做的不需要启动子进程,称为内置命令),并等待(waitpid)子进程完成并退出。那么进程的内存映像显然包含了本文的主题环境变量。比如我们在shell命令行中执行ls-al,shell实际上执行的伪代码如下:pid=fork();if(pid==0){//在子进程中,调用execexec("ls-al");}elseif(pid>0){//父进程中waitpid等待子进程退出waitpid(pid);}上面讨论了shell执行命令的情况。如果在命令行执行shell脚本怎么办?默认情况下,shell进程会创建一个sub-shell子进程来执行shell脚本并等待子进程完成。最后,我们再来看一下本文的主题。首先,set、source、export都是shell内置命令,命令本身不会创建新进程。事实上,set与进程创建或环境变量无关。它只是当前shell进程内部维护的一个变量(局部变量),用于变量的引用和扩展,不能被继承和继承。但是shell的export命令可以通过调用putenv将一个局部变量提升为当前shell的环境变量。但是,请记住,环境变量的继承只是单向的,子shell中导出的变量对父shell是不可见的。有没有办法在脚本中对父进程的环境变量进行导出印象?答案是使用source来执行脚本。source的用法如下:source./test.sh如果使用source执行脚本,则表示不会调用fork和exec,当前shell直接解释执行test.sh。这样的话,如果此时test.sh中有export(putenv),就会改变当前shell的环境变量。export这么好用,问题是它会影响到几乎所有后续的命令。有没有办法在运行某个命令时临时启用环境变量而不影响后续命令?答案是使用env。env的用法如下:envGOTRACEBACK=crash./test.shenv不是shell的内建命令,所以shell在执行env的时候,还是需要创建一个子进程。env的作用本质上相当于shell先fork,然后在子进程中运行env。子进程env在调用execve运行test.sh的时候,额外传递了一个GOTRACEBACK=crash的环境变量(上面说了execve可以改变默认的继承行为),所以test.sh可以看到这个GOTRACEBACK环境变量,但是由于不调用putenv改变父shell的环境变量,后续启动的进程不继承GOTRACEBACK。exec就是不调用fork,而是直接调用exec执行!意思是当前shell的代码执行到exec后,将代码替换为exec要执行的程序。后面的shell脚本自然不会执行,因为shell本身已经被替换了。上图中的env其实并不准确,因为env不是内置命令,读者可以自行脑补。仅仅从理论上消化可能不是那么容易。不如动手实践的“实践+思考”印象深刻。.问题一:写两个简单的脚本,分别命名为1.sh和2.sh:1.sh#!/bin/bashA=Becho"PIDfor1.shbeforeexec/source/fork:$$"exportAecho"1.sh:\$Ais$A"case$1inexec)echo"usingexec..."exec./2.sh;;源)回显“使用源……”../2.sh;;*)echo"默认使用fork..."./2.sh;;esacecho"exec/source/fork后1.sh的PID:$$"echo"1.sh:\$Ais$A"2.sh#!/bin/bashecho"PIDfor2.sh:$$"echo"2.shget\$A=$Afrom1.sh"A=CexportAecho"2.sh:\$Ais$A"Then,分别运行观察结果,参数如下:$./1.shfork$./1.shsource$./1.shexec问题2:用env设置环境变量后,运行中调用了其他脚本脚本。这个环境变量会不会继续?
