onetozero 2019-12-19
Shell 变量的作用域(Scope),就是 Shell 变量的有效范围(可以使用的范围)。Shell 变量的作用域可以分为三种:
原理 | 代码 | 输出结果 | 结论 |
---|---|---|---|
Shell 支持自定义函数,但是 Shell 函数和其他编程语言函数的一个不同点就是: 在 Shell 函数中定义的变量默认也是全局变量,它和在函数外部定义变量拥有一样的效果。 | #!/bin/bash #定义函数 function func(){ a=99 } #调用函数 func #输出函数内部的变量 echo $a | 99 | a 是在函数内部定义的,但是在函数外部也可以得到它的值, 证明它的作用域是全局的,而不是仅限于函数内部。 |
要想变量的作用域仅限于函数内部, 可以在定义时加上 | #!/bin/bash #定义函数 function func(){ local a=99 } #调用函数 func #输出函数内部的变量 echo $a | 输出结果为空 | 输出结果为空,表明变量 a 在函数外部无效,是一个局部变量。 Shell 变量的这个特性和 JavaScript 中的变量是类似的。 在 JavaScript 函数内部定义的变量,默认也是全局变量, 只有加上 |
在 Shell 中定义的变量,默认就是全局变量,所谓全局变量,就是指变量在当前的整个 Shell 进程中都有效。
需要强调的是,全局变量的作用范围是当前的 Shell 进程,而不仅仅是当前的 Shell 脚本文件,它们是不同的概念。打开一个 Shell 窗口就创建了一个 Shell 进程,打开多个 Shell 窗口就创建了多个 Shell 进程,每个 Shell 进程都是独立的,拥有不同的进程 ID。在一个 Shell 进程中可以使用 source 命令执行多个 Shell 脚本文件,此时全局变量在这些脚本文件中都有效。
关注点 | 操作 | 对比1 | 对比2 | 其他 |
---|---|---|---|---|
全局变量的作用范围是当前shell进程 | 打开两个shell窗口,验证变量作用域 | 打开一个 Shell 窗口,定义一个变量 b 并赋值为 88,然后打印,这时在同一个 Shell 窗口中是可正确打印变量 b 的值的。 | 再打开一个新的 Shell 窗口,同样打印变量 b 的值,但结果却为空。 | |
全局变量的作用范围是不仅仅在当前的 Shell 脚本文件 | 在当前shell窗口定义变量a,可以被脚本1使用,在脚本1中定义的变量b,可以在脚本2中使用 | a.sh
| b.sh
| 这三条命令都是在一个进程中执行的,从输出结果可以发现,在 Shell 窗口中以命令行的形式定义的变量 a,在 a.sh 中有效;在 a.sh 中定义的变量 b,在 b.sh 中也有效,变量 b 的作用范围已经超越了 a.sh。 |
在Shell 窗口,输入以下命令 a=99 . ./a.sh -->99 | 在同样的shell中执行. ./b.sh -->200 |
脚本 | 分析 |
---|---|
PARAM_NUMBER=0; cat "/home/roaddb/test/55.txt" | while read line do let PARAM_NUMBER=${PARAM_NUMBER}+1; done echo "${PARAM_NUMBER}" | 结果PARAM_NUMBER的值还是0,原因是在进行 cat的过程中, 相当于打开了一个新的shell,变量不在作用范围。 |
#!/bin/bash PARAM_NUMBER=0; while read line do let PARAM_NUMBER=${PARAM_NUMBER}+1; done<"/home/roaddb/test/55.txt" echo "${PARAM_NUMBER}" | PARAM_NUMBER的值是正确的 |
脚本 | 知识点 | |
---|---|---|
#! /bin/bash echo "PID of this script: $$" echo "PPID of this script: $PPID" | echo $$ 输出当前进程号-------->590 echo $PPID 输出父进程号-------->589 | |
执行方式 | 结果 | |
a.sh | 报错bash: a.sh: command not found | |
./a.sh | bash: ./a.sh: Permission denied 需要权限,设置权限为777后执行 PID of this script: 92669 PPID of this script: 590 | 启了一个子shell来执行a.sh,所以可以看到PPID of this script: 590 |
sh ./a.sh | PID of this script: 92878 PPID of this script: 590 | |
sh a.sh | PID of this script: 92928 PPID of this script: 590 | |
source ./a.sh | PID of this script: 590 PPID of this script: 589 | 在当前shell中执行脚本 |
source a.sh | PID of this script: 590 PPID of this script: 589 | |
. ./a.sh 两个点之间有空格 | PID of this script: 590 PPID of this script: 589 | |
../a.sh | 报错bash: ../a.sh: No such file or directory |
最后要说明的两点是:
1. 用sh和source去执行时, 不要求a.sh有可执行权限, 但单独./a.sh这样去搞时,需要可执行权限
2. 大家在开发项目时,经常需要设置环境变量, 当然是用source啊, 确保在当前shell生效
全局变量只在当前 Shell 进程中有效,对其它 Shell 进程和子进程都无效。如果使用export
命令将全局变量导出,那么它就在所有的子进程中也有效了,这称为“环境变量”。
环境变量被创建时所处的 Shell 进程称为父进程,如果在父进程中再创建一个新的进程来执行 Shell 命令,那么这个新的进程被称作 Shell 子进程。当 Shell 子进程产生时,它会继承父进程的环境变量为自己所用,所以说环境变量可从父进程传给子进程。不难理解,环境变量还可以传递给孙进程。
注意,两个没有父子关系的 Shell 进程是不能传递环境变量的,并且环境变量只能向下传递而不能向上传递,即“传子不传父”。
使用env可以查看所有环境变量!!!
创建 Shell 子进程最简单的方式是运行 bash 命令,如图 2 所示。
图2:进入 Shell 子进程
通过exit
命令可以一层一层地退出 Shell。
下面演示一下环境变量的使用:
[c.biancheng.net]$ a=22 #定义一个全局变量 [c.biancheng.net]$ echo $a #在当前Shell中输出a,成功 22 [c.biancheng.net]$ bash #进入Shell子进程 [c.biancheng.net]$ echo $a #在子进程中输出a,失败 [c.biancheng.net]$ exit #退出Shell子进程,返回上一级Shell exit [c.biancheng.net]$ export a #将a导出为环境变量 [c.biancheng.net]$ bash #重新进入Shell子进程 [c.biancheng.net]$ echo $a #在子进程中再次输出a,成功 22 [c.biancheng.net]$ exit #退出Shell子进程 exit [c.biancheng.net]$ exit #退出父进程,结束整个Shell会话
可以发现,默认情况下,a 在 Shell 子进程中是无效的;使用 export 将 a 导出为环境变量后,在子进程中就可以使用了。export a
这种形式是在定义变量 a 以后再将它导出为环境变量,如果想在定义的同时导出为环境变量,可以写作export a=22
。
我们一直强调的是环境变量在 Shell 子进程中有效,并没有说它在所有的 Shell 进程中都有效;如果你通过终端创建了一个新的 Shell 窗口,那它就不是当前 Shell 的子进程,环境变量对这个新的 Shell 进程仍然是无效的。请看下图:第一个窗口中的环境变量 a 在第二个窗口中就无效。
环境变量也是临时的,通过 export 导出的环境变量只对当前 Shell 进程以及所有的子进程有效,如果最顶层的父进程被关闭了,那么环境变量也就随之消失了,其它的进程也就无法使用了,所以说环境变量也是临时的。
那如何让一个变量在所有 Shell 进程中都有效,不管它们之间是否存在父子关系呢?只有将变量写入 Shell 配置文件中才能达到这个目的!Shell 进程每次启动时都会执行配置文件中的代码做一些初始化工作,如果将变量放在配置文件中,那么每次启动进程都会定义这个变量。修改配置文件可参考《Shell配置文件的加载》《编写自己的Shell配置文件》。