资讯专栏INFORMATION COLUMN

set,env,export,source,exec傻傻分不清楚?

taoszu / 2420人阅读

摘要:设置了当前进程的本地变量,本地变量只在当前的进程内有效,不会被子进程继承和传递。一个进程可以生成另一个进程,生成的进程称为子进程,那么相应的就有父进程,所谓子子孙孙无穷尽也。和在系统中进程通过依次调用和系统调用来实现创建一个子进程。

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

什么是export,什么时候用export,为什么有时用了export还要source

为什么用env来设置环境变量,不用export,有什么好处?

sourceexec有什么区别?

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

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

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()函数等待子进程退出,并获得退出状态。

进程可通过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本身都被替换掉了。

上图的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://www.ucloud.cn/yun/10476.html

相关文章

  • webpack多页应用架构系列(七):开发环境、生产环境傻傻分不清楚

    摘要:开发环境和生产环境都拥有的配置,但在细节上有所不同,比如说,又比如说中的和参数。更重要的是,实际上开发环境和生产环境的配置文件的绝大部分都是一致的,对于这一致的部分来说,我们坚决要消除冗余,否则后续维护起来不仅麻烦,而且还容易出错。 本文首发于Array_Huang的技术博客——实用至上,非经作者同意,请勿转载。原文地址:https://segmentfault.com/a/11900...

    paulquei 评论0 收藏0
  • 傻傻分不清的Manifest

    摘要:的英文含义是名单种技术的确都是把当做清单使用缓存清单清单打包资源路径清单打包清单只不过是在不同的场景中使用特定的清单来完成某些功能所以,学好英文是多么重要,这样才不会傻傻分不清到底是干啥的 在前端,说到manifest,其实是有歧义的,就我了解的情况来说,manifest可以指代下列含义: html标签的manifest属性: 离线缓存(目前已被废弃) PWA: 将Web应用程序...

    printempw 评论0 收藏0
  • 傻傻的分也分不清楚的property和attribute

    摘要:最近,一个小伙伴问了我一个问题和的区别当时我想了又想,很不好意思的说了我不知道,所以,抽了个事件好好的利用了一下度娘和总结了一下。 最近,一个小伙伴问了我一个问题property和attribute的区别?当时我想了又想,很不好意思的说了我不知道,所以,抽了个事件好好的利用了一下‘度娘’和‘Google’总结了一下。度娘搜索到的有用信息知乎中的讨论csdn搜索的结果,Google发现的...

    SimpleTriangle 评论0 收藏0
  • Unix 网络 IO 模型: 同步异步, 傻傻分不清楚?

    摘要:出处阻塞非阻塞同步异步这些术语相信有不少朋友都也不同程度的困惑吧我原来也是什么同步非阻塞异步非阻塞的搞的头都大了后来仔细读了一遍网络编程卷一套接字联网第三版的章节终于把这些名词搞懂了下面我以网络编程卷一套接字联网第三版的章节的内容为准整理了 出处 阻塞 IO, 非阻塞 IO, 同步 IO, 异步 IO 这些术语相信有不少朋友都也不同程度的困惑吧? 我原来也是, 什么同步非阻塞 IO, ...

    1fe1se 评论0 收藏0
  • C++入门—namespace的使用傻傻分不清楚&C++中函数的参数也可以配备胎&a

    摘要:使用输入输出更方便,不需增加数据格式控制,比如整形,字符等缺省参数备胎缺省参数是声明或定义函数时为函数的参数指定一个默认值。此外,函数重载要求参数不同,而跟返回值没关系。 ...

    pingan8787 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<