聊聊Linux Shell

系列文章首发平台为[果冻想个人博客][1]。果冻想,是一个原创技术文章分享网站。在这里果冻会分享他的技术心得,技术得失,技术人生。我在[果冻想][1]等待你,也希望你能和我分享你的技术得与失,期待。

果冻想-一个原创技术文章分享网站

为什么写这篇文章

那天同事在我的电脑上给我演示一个操作的步骤,使用了一个env命令,好吧,我承认我文盲,我不知道这个命令是干嘛的!!!正是由于我的无知,反倒激起了学习探索的欲望,一定要把这个env学个透彻。经过几个下班后的空闲时间,终于搞清楚了这“坨”东西,记录成文,和大家分享,如果对大家有所帮助,或者能够扫除大家的一些盲点,我也就胜感欣慰。

对Shell最简单的认识

这就是你眼中的Shell,当你在Linux中,打开Terminal的时候,出现了一个黑屏,又或一个白屏的窗口的,哦,这就是我们眼中的Shell,这种理解也许对,也许不对。

这是Linux Shell

当我们打开Terminal的时候,其实运行了一个默认的Shell解释器,我们一般都是/bin/bash,当我们在终端中输入各种命令时,都是由这个/bin/bash来进行解释的;也就是说Shell是运行在Terminal上的一个程序。

明白了Shell以后,我们经常会在当前的Shell中运行一个Shell脚本,当你运行这个Shell脚本的时候,你是否知道这背后在干些什么么?这又说到下一个话题:父Shell与子Shell。

父Shell与子Shell

如果不明白父Shell与子Shell的关系,那么这篇文章重点要讲的Shell中的变量,也就说不清了。

当我们启动Terminal的时候,会运行一个Shell进程,暂且叫做Shell进程A;当我们在这个Terminal中运行Shell脚本的时候,进程A会fork出一个新进程,从而启动另一个Shell解释器(这由脚本中第一行指定的,例如:#!/bin/bash),fork出来的这个新进程暂且叫做进程B。此时,进程A和进程B就是父、子进程的关系;进程B是一个子Shell,而进程A则是父Shell。一旦子Shell中的脚本执行完毕,此子Shell随即结束,返回到父进程。这就是父Shell和子Shell。

我想我应该把父Shell和子Shell说清了,明白了这层关系以后,我们继续总结。

Shell初始化读的一些配置文件

首先,你要意识到一点,就是你登陆Linux系统,打开Terminal,使用Shell的时候,系统在背后读取了一大“坨”的配置文件,这些配置文件决定了你的Shell中的变量。所以,在具体总结Shell中的变量时,我们还需要来看看读了哪些配置文件,以及读取这些配置文件的顺序是什么。

  1. /etc/profile:该文件为系统的每个用户设置环境信息,当用户第一次登录系统时,该文件会被执行。 并从/etc/profile.d目录的配置文件中读取shell的设置;
  2. ~/.bash_profile或者~/.bash_login或者~/.profile:系统会依次寻找这三个文件,这些是针对当前用户的配置,但是需要注意的是,这三个文件一般不会同时存在,即使同时存在,系统按照这个顺序找到了一个以后,就不会再去读剩下的了;
  3. ~/.bashrc:该文件包含了专属于当前登录用户的bash shell的bash信息,当登录以及每次打开新的shell时,该文件都会被读取;
  4. /etc/bashrc:为每一个运行bash shell的用户执行此文件;当bash shell被打开时,该文件被读取。

这些脚本配置文件决定了你的系统变量和环境变量等。如果你感兴趣,你可以去看看这些脚本的源码,你就会知道它们到底是怎么调用的了。当然了,如果我们需要定义一些我们经常用到的变量,比如配置JDK的时候,你可能还要编辑它们。

Shell中的变量

先不带子Shell玩,把单个Shell进程中的变量捋清了,再把子Shell加进来一起总结。

在Shell中有以下三种变量:

  • 内部变量;系统定义,不能修改;
  • 环境变量;系统定义,可以修改,可以利用export将用户变量转为环境变量;
  • 用户变量;用户定义,可以定义,玩坏了都没事。

比如以下这些就是内部变量:

变量名 描述
$# 命令行参数个数
$0 当前程序的名称
$? 前一个命令或函数的返回码
$$ 当前程序的PID

以下这些是我们常用的一些环境变量,一般我们使用env命令查看当前用户的环境变量。

变量名 描述
PATH 表示Shell将到哪些目录中寻找命令或程序
SHELL 当前用户的Shell类型
HOME 当前用户主目录
PS1 基本提示符

用户变量(本地变量)那就比较随性了,你可以自己随意定义,比如:

> str='Hello World'
> echo $str

我们使用set命令来显示当前Shell中定义的用户变量,当然了set命令也会输出环境变量。

上面说的这些你明白了么?我们继续。

带上子Shell一起玩

当我们在Shell里面再运行一个Shell脚本的时候,这个时候会fork出一个新的Shell进程,此时就会有父、子Shell两个进程了。有了父、子关系,那么父进程中的Shell变量会遗传到子进程中么???同时,子进程中的Shell变量会返回到父进程么???这些都是我们需要关注的。先来看一个例子:

父Shell定义一个变量:

> str='Hello World'

然后在父Shell中运行以下脚本:

#!/bin/bash
# 输出父Shell中定义的str
echo $str

# 输出环境变量
echo $HOME
echo $PATH

你会发现,运行以后,str HOME和$PATH却可以很完美的输出。这也说明,我们在一个Shell中定义的用户变量,只能被当前Shell所使用,别人是无法访问到的,即使是子Shell也不例外;而父进程的环境变量是可以在子进程中被访问。但是有的时候,我们有这样的需求:

在子进程中访问父进程的用户变量(本地变量),这该怎么办???

当我们遇到这样的需求时,我就不得不说一下export命令了。

说说export命令

export命令可以将用户变量设置为环境变量,从而可以在子Shell进程中访问该变量。这正好也好和export的中文含义相符。对于之前的例子,我们可以在父Shell中输入一下命令:

> export str

再次执行脚本时,就可以输出用户变量str的值了;这就是export的作用。但是,我们在父Shell中输入export str以后,当我们关闭父Shell以后,该环境变量将失效,如果想打开Shell就能立刻设置export,我们可以按照我们的需要,将export str写到上面总结的那一“坨”启动配置文件中,这样就不会因为关闭了父Shell而导致export失效。

父与子的另一层关系

现在我们可以在子进程中访问父进程的变量了,你是否想过,在子Shell中修改父Shell的变量是否会影响父Shell中该变量的值呢?不妨做个测试。

#!/bin/bash
# 修改父Shell传递过来的变量str的值
$str='http://www.jellythink.com'
echo $str

运行脚本,发现在父Shell中,str的值并没有发生改变。其实,这又关系到Linux中关于进程的另一个知识点。当父进程中fork一个子进程时,子进程会拷贝父进程的相关变量,此时,子进程就会拥有和父进程同名同值的变量,虽然同名同值,但是却只是父进程的一个副本,对于副本的修改都和父进程无关。

而如果我们在子Shell中定义一个变量,反过来在父Shell中是否可以访问呢?实践告诉我们,这样是行不通的,你不可以在父Shell中访问子Shell中定义的变量。如果想在父Shell中访问子Shell中定义的变量,可以借助一个临时文件,将局部变量写入临时文件,父Shell读取这个文件,从而达到访问子Shell中定义的变量的目的。

总结

这篇文章虽然总结的多而杂,但是每一部分都是息息相关、环环相扣的,对于大家整体理解Linux Shell有非常大的帮助,也有利于大家学习Linux Shell。文章有点长,需要一点耐心去把它从头到尾好好的读完。为了学习,是需要一点耐心的,你说呢?这一篇就总结到这里了,下一篇再见。

果冻想-一个原创技术文章分享网站。

2015年10月21日 于呼和浩特。

附录

这里对文中涉及到的命令以及一些相关命令进行总结一下。

命令 描述
set 显示本地定义的Shell变量
unset 清除环境变量,例如:unset str
export 设置一个新的环境变量
env 显示当前用户所有环境变量

如果你觉的文章还不错,可以关注果冻想微信公众号,定期推送技术文章:
果冻想微信公众号

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章