举例说明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】修饰的变量,那就尝试通过【命令输出】判断命令执行的结果,而不是通过【$?】来判断。

 

八、来自网友的建议

  可以考虑如下写法,将定义和赋值分开

 1 #! /bin/bash
 2 
 3 _with_export()
 4 {
 5         export E_LINE_CMD=""
 6         E_LINE_CMD="$(./yjcmd)"
 7         echo cmd output [ $E_LINE_CMD ], cmd return [ $? ]
 8 }
 9 
10 _with_local()
11 {
12         local L_LINE_CMD=""
13         L_LINE_CMD="$(./yjcmd)"
14         echo cmd output [ $L_LINE_CMD ], cmd return [ $? ]
15 }
16 
17 _default()
18 {
19         D_LINE_CMD="$(./yjcmd)"
20         echo cmd output [ $D_LINE_CMD ], cmd return [ $? ]
21 }
22 
23 
24 E_LINE_CMD="defvalue"
25 L_LINE_CMD="defvalue"
26 D_LINE_CMD="defvalue"
27 
28 
29 echo before function, export  [ $E_LINE_CMD ]
30 echo before function, local   [ $L_LINE_CMD ]
31 echo before function, default [ $D_LINE_CMD ]
32 
33 
34 echo
35 _with_export
36 _with_local
37 _default
38 
39 
40 echo
41 echo after function, export  [ $E_LINE_CMD ]
42 echo after function, local   [ $L_LINE_CMD ]
43 echo after function, default [ $D_LINE_CMD ]
44 
45 
46 echo
47 ./yjchildshell.sh

执行结果如下

./yjshell.sh
before function, export [ defvalue ]
before function, local [ defvalue ]
before function, default [ defvalue ]

cmd output [ yjtest ], cmd return [ 1 ]
cmd output [ yjtest ], cmd return [ 1 ]
cmd output [ yjtest ], cmd return [ 1 ]

after function, export [ yjtest ]
after function, local [ defvalue ]
after function, default [ yjtest ]

In child shell, export [ yjtest ]
In child shell, local [ ]
In child shell, default [ ]

这样来看,命令返回值是正确的了

 

posted @ 2022-02-21 18:28  J&YANG  阅读(4240)  评论(2)    收藏  举报