举例说明Linux shell中local、export的常用用法和要注意的问题


一、前言

  在【Linux shell】中,【local】和【export】通常被拿来控制shell中变量的作用域。export被用到的场合会更多一些,local只能被用在shell函数中。

二、直接上例子

  为了方便理解,尝试写三个简单的shell脚本。

  命令【yjcmd】:首先用shell模拟一个有输出的,返回值为错误值的命令。

  shell【yjshell.sh】:写一个【local】、【export】和【默认shell变量】放在一起对比的【例子shell】。

  子shell【yjchildshell.sh】:为了表达变量在shell间传递,写一个【子shell】,【子shell】中尝试访问【yjshell.sh】中的【三种】类型的变量。

  1、【yjcmd】

    这个命令的作用是输出【yjtest】,并且返回【1】,代表命令执行错误。

  2、【yjshell.sh】

  3、【yjchildshell.sh】

三、执行结果

     

    注:【bash】和【dash】中,结果都如上图所示,没有差别。

四、基本规律总结

  基本规律大多数常写shell脚本的人都非常清楚,这里不过多讨论,简单总结下。

  从【三、】的执行结果中可以清晰看出:

  1、默认变量可以在【yjshell.sh】的任意位置访问到,但是【不能】被【子shell】【yjchildshell.sh】继承。

  2、被【local修饰】的变量只在【当前shell函数】中生效,【yjshell.sh】的其它地方并不能得到这个变量的值,并且【子shell】也无法继承它。

  3、被【export修饰】的变量其实已经成为了【当前shell】的【环境变量】,可以被【当前shell】以及【当前shell调用的子shell】访问到。

  注:虽然export修饰的变量【作用范围变大】,但是其仅限于【当前shell环境】和【被其调用的子shell】中,并不能影响到其【父shell】(这里例子没有展示,简单来说就是当前shell环境中export的内容,当前shell的父shell无法访问)。

五、例子中的坑

  我们可以看到例子中,关于函数内的打印,多了一个【$?】的输出,【$?】是用来获取上一条命令执行结果值的。

  可以看到【yjcmd】这个命令中,命令的输出内容是【yjtest】,命令的返回值是【1】,想要表达这个命令执行出错了,返回了错误值。

  通常情况下,shell脚本中的变量赋值并不会影响【$?】的数值,因为变量赋值不属于命令范畴。

  但是当命令执行的输出被赋值给三种类型变量后,【$?】的输出却并不相同。

六、关于坑的探索

  出现这个问题的初期,确实挺令人不解的,明明只是几个不同作用域的变量,被【yjcmd】命令的输出赋了值,为何会导致后续的【$?】命令取值发生了变化。

  带着这个问题探索了很长时间的百度,并没有什么收获。

  后来回归【local】和【export】的定义时,被“点醒”了。

  【local】和【export】的定义中,对【local】和【export】的名词定性为【命令】。

  我们都知道,当描述一个物体时,通常会用形容词+名词的方式进行表达,形容词通常用来说明这个【物体的性质】,名词用来表达这个【物体的本质】。

  例如面试中最为经典的【指针数组】和【数组指针】的概念区分,英文描述中借助的就是这个原理,用名词部分区分这两个概念的本质。

  既然【local】和【export】的本质被定义为【命令】,那么这就代表【local】和【export】就具备了命令的通用特性,具备【返回值】。

  所以说例子中,命令执行,命令输出被赋值给了变量,变量被【local】和【export】修饰相当于执行了一条命令。

  换句话说,被修饰的变量作用域缩小或是扩大是【local】和【export】命令作用的结果,因此【$?】的值就是【local】和【export】命令执行的返回值。

  因此也就可以解释为什么【yjcmd】明明返回的是【1】,但是【$?】的输出确实【0】了。

七、关于坑的总结

  这个小小的问题如果不被关注,可能会导致一个巨大的bug,这里记录下来用来给自己提个醒。

  需要获取命令执行成功与否时,如果想通过返回值的方式进行判断,那么就不要将命令的输出【直接赋值】给【local】【export】修饰的变量。

  如果必须要【直接赋值】给【local】或【export】修饰的变量,那就尝试通过【命令输出】判断命令执行的结果,而不是通过【$?】来判断。