Linux下set,env,export,source,exec深入解析

你是否被下面的几个问题困扰过,甚至至今无法真正理解?

  1. 什么是export,什么时候用export,为什么有时用了export还要source
  2. 为什么用env来设置环境变量,不用export,有什么好处?
  3. sourceexec有什么区别?

本文试图通过普及unix进程、环境变量等概念,让读者真真理解这些shell命令的本质,知道这些命令的使用场合。

clipboard.png

首先,先对这些命令做一个解释,如果读者能完全理解,那么本文也许对你帮助不大。

  • set设置了当前shell进程的本地变量,本地变量只在当前shell的进程内有效,不会被子进程继承和传递。
  • env仅为将要执行的子进程设置环境变量
  • export将一个shell本地变量提升为当前shell进程的环境变量,从而被子进程自动继承,但是export的变量无法改变父进程的环境变量。
  • source运行脚本的时候,不会启用一个新的shell进程,而是在当前shell进程环境中运行脚本。
  • exec运行脚本或命令的时候,不会启用一个新的shell进程,并且exec后续的脚本内容不会得到执行,即当前shell进程结束了。

在这些表述中,反复提到进程环境变量的概念。如果希望深入理解其中的含义,还必须理解进程的相关概念。

进程和环境变量

进程是一个程序执行的上下文集合,这个集合包括程序代码、数据段、堆栈、环境变量、内核标识进程的数据结构等。一个进程可以生成另一个进程,生成的进程称为子进程,那么相应的就有父进程,所谓子子孙孙无穷尽也。子进程父进程处会继承一些遗传因素,其中就包括本文的主题环境变量。环境变量是一组特殊的字符型变量,由于具有继承性质,环境变量也经常用于父子进程传递参数用,这一点在shell编程中尤为突出。

fork和exec

在unix系统中进程通过依次调用fork()exec()系统调用来实现创建一个子进程。
fork其实就是克隆,为什么github复刻别人的项目叫fork?就是这么来的,所谓“克隆”,就是在内存中将当前进程的所有内存镜像复制一份,所有东西都一样,只修改新进程的进程号(PID)。有点类似细胞分裂,细胞分裂后生成的细胞具有与原细胞完全相同的遗传因素。因为fork()会复制整个进程,包括进程运行到哪句代码,这意味着新的进程会继续执行fork()后面的代码,父进程也会运行fork()后面的代码,从fork()开始父子进程才分道扬镳。如果fork返回>0,那么说明在父进程中,如果fork返回==0,说明在子进程中:

pid = fork();
if(pid == 0) {
  //子进程中
} else if(pid > 0) {
  //父进程
}

精确的说exec是一组函数的统称,并且exec的准确定义是,用磁盘上的一个新的程序替换当前的进程的正文段、数据段、堆栈段。所以exec并不产生新的进程,而是替换。如此一来进程将从新代码的main开始执行,相当于另外运行了一个完全不同的程序,但保留了原来环境变量。

依据本文的主题,可以把exec函数分为两类,一类是可以设置并传递新环境变量的,一类是不能传递新环境变量的,只能继承原环境变量的。换句话说,在运行新的程序时,是有机会改变新程序的环境变量的,而不只是继承。如下面这个变种,可以通过envp参数设置环境变量

int execve(const char * filename,char * const argv[ ],char * const envp[ ]);

作为父进程而言,可以通过waitpid()函数等待子进程退出,并获得退出状态。

clipboard.png

进程可通过setenvputenv更改自己的环境变量,但环境变量的继承只能单向,即从父进程继承给fork出来的子进程。子进程即使修改了自己的环境变量也无法动摇到父进程的环境变量。

shell

shell并没有什么特殊,也是一个进程,当我们在命令行中敲入一个命令,并且按下Enter后,shell这个进程会通过fork和exec为我们创建一个子进程(存在一小部分命令不需要启动子进程,称为build-in命令),并且等待(waitpid)这个子进程完成退出。那么进程的内存镜像显然就包含本文的主题环境变量。比如,如果我们在shell命令行中执行ls -al,shell实际执行如下伪代码:

pid = fork();
if(pid == 0) {
  //子进程中,调用exec
  exec("ls -al");
} else if(pid > 0) {
  //父进程中,waitpid等待子进程退出
  waitpid(pid);
}

上面讨论了shell执行命令的情况,如果在命令行中执行一个shell脚本呢?默认情况下,shell进程会创建一个sub-shell子进程来执行这个shell脚本,并且等待这个子进程执行结束。

最后,再来审视一下本文的主题。首先set,source,export都是shell的build-in命令,命令本身不会创建新进程。

set其实跟进程创建无关,也跟环境变量无关,它只是当前shell进程内部维护的变量(本地变量),用于变量的引用和展开,不能遗传和继承。

但shell的export命令可以通过调用putenv将一个本地变量提升为当前shell的环境变量。但是,记住环境变量的继承只是单向的,sub-shellexport的变量在父shell中是看不到的。有什么办法可以让一个脚本中的export印象到父进程的环境变量呢?

答案是使用source执行脚本,source的用法如下:

source ./test.sh

如果用source执行脚本,意味着fork和exec不会被调用,当前shell直接对test.sh解释执行。这样的话,如果此时test.sh中有export(即putenv),那么将会改变当前shell的环境变量。

export如此好用,但是问题是它几乎会影响到其后的所有命令,有没有办法可以在运行某个命令时,临时启用某个环境变量,而不影响后面的命令呢?

答案是使用envenv的用法如下:

env GOTRACEBACK=crash ./test.sh
env不是shell的build-in命令,所以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本身都被替换掉了。

clipboard.png

上图的env实际并不准确,因为env不是build-in命令,读者可自行脑补

嗯,光是从理论去理解,或许没那么好消化,不如动手“实作+思考”来的印象深刻哦。

问题一:写两个简单的script,分别命名为1.sh及2.sh:

1.sh

#!/bin/bash
A=B
echo "PID for 1.sh before exec/source/fork:$$"
export A
echo "1.sh: \$A is $A"
case $1 in
    exec)
        echo "using exec…"
        exec ./2.sh;;
    source)
        echo "using source…"
        ../2.sh;;
    *)
        echo "using fork by default…"
        ./2.sh;;
esac
echo "PID for 1.sh after exec/source/fork:$$"
echo "1.sh: \$A is $A"

2.sh

#!/bin/bash
echo "PID for 2.sh: $$"
echo "2.sh get \$A=$A from 1.sh"
A=C
export A
echo "2.sh: \$A is $A"

然后,分别跑如下参数来观察结果:

$ ./1.sh fork
$ ./1.sh source
$ ./1.sh exec

问题二:用env设置环境变量后,运行的脚本中又调用了其他脚本,这个环境变量还会继承下去吗?

转自:https://segmentfault.com/a/1190000013356532

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在Linux系统中设置"CHROME_BIN"环境变量的方法有多种。 一种方法是在命令行中直接输入: ``` export CHROME_BIN=/usr/bin/google-chrome ``` 或者将其加入到`.bashrc`或`.bash_profile`文件中: ``` echo "export CHROME_BIN=/usr/bin/google-chrome" >> ~/.bashrc ``` 还可以在当前会话中设置: ``` CHROME_BIN=/usr/bin/google-chrome ``` 其中,"/usr/bin/google-chrome"是chrome可执行文件的路径,在不同的系统中可能不同,请根据实际情况修改. ### 回答2: 要设置"CHROME_BIN"环境变量,需要按照以下步骤进行操作: 1. 打开终端,使用以下命令来编辑bash配置文件: ``` vi ~/.bashrc ``` 2. 在文件的末尾添加以下行: ``` export CHROME_BIN=/usr/bin/google-chrome ``` 注意,这里的`/usr/bin/google-chrome`是你安装的Chrome浏览器的路径,根据实际情况进行修改。 3. 保存并退出编辑器,然后使用以下命令使更改生效: ``` source ~/.bashrc ``` 4. 现在,可以在终端中使用`echo`命令来验证变量是否设置成功: ``` echo $CHROME_BIN ``` 如果显示了你设置的Chrome浏览器路径,则表示设置成功。 设置"CHROME_BIN"环境变量后,你可以在脚本或命令中使用它。这是因为某些应用或脚本需要知道Chrome浏览器的路径才能正常工作。 ### 回答3: 要在Linux系统中设置"CHROME_BIN"环境变量,可以按照以下步骤进行操作: 1. 首先,打开终端或命令行界面,这里我们使用bash shell作为示例。 2. 使用以下命令导出并设置"CHROME_BIN"环境变量的值: ``` export CHROME_BIN=/usr/bin/google-chrome ``` 在这个例子中,我们将"CHROME_BIN"环境变量设置为"/usr/bin/google-chrome",这是Chrome浏览器的默认安装路径。 3. 验证环境变量是否正确设置。可以使用以下命令来查看设置的环境变量: ``` echo $CHROME_BIN ``` 如果正确设置了"CHROME_BIN"环境变量,命令的输出应该是"/usr/bin/google-chrome"。 4. 如果您希望在每次启动时都自动加载该环境变量,可以将上述命令添加到您的bash配置文件中(例如:~/.bashrc或~/.bash_profile)中。这样,在每次登录到Linux系统时,都会自动设置该环境变量。 请注意,上述步骤假设您已经在系统上正确安装了Chrome浏览器,并且它们的文件路径与示例中提供的一致。如果您的Chrome浏览器安装路径不同,请相应地更改"CHROME_BIN"环境变量的值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值