有关subshell

总算不是那么的懵逼了。

基本概念

本质上是一个完整的新shell,用来跑指定的程序。

看个简单的例子:

x=100

cat test
# 输出
#!/usr/bin
echo x is $x

sh test
# 输出
x is

这里,当执行sh test 时,login shell 「即parent shell」会创建一个新的shell来跑 test 程序,这个新的shell有着自己的环境变量,并且对parent shell中的本地变量local variable一无所知,test中的变量 x 默认赋值为null,所以输出结果为x is.

获取 parent shell 的 local variable

两种方法:

  • export

    export 给 subshell 的变量,其实是复制了一份 parent shell 中的 local variable,所以针对 exported variable 所做的改变, 都不会影响到 parent shell 原先的同名变量

    例子:

    x=10
    cat var_test
    # 输出
    #!/bin/bash
    echo $x
    if [ -n $x ]; then
        x=$(($x+1))
        echo $x
    fi
    
    sh var_test
    # 输出,此时在var_test中 x 为 null, [ -n $x ] 为 false, 所以什么都没有输出
    
    export x
    sh var_test
    # 输出, 此时在var_test中,对 x 进行了修改,但并不影响login shell中的 x
    10
    11
    
    echo $x
    # 输出, 这里,x 依然是10
    10
    

    exported variables 可以一直传给后面的subshell:

    cat var_test3
    # 输出
    y=9
    z=8
    echo x = $x
    echo y = $y
    echo z = $z
    export z
    sh var_test4
    
    cat var_test4
    # 输出
    echo x = $x
    echo y = $y
    echo z = $z
    
    sh var_test3
    # 输出
    x =
    y = 9
    z = 8
    x =
    y =
    z = 8
    
    x=100
    y=90
    export x
    export y
    sh var_test3
    # 输出
    x = 100
    y = 9
    z = 8
    x = 100
    y = 9
    z = 8
    

    这里会发现 export 给 var_test3 的变量 y,初始值为90 , 在 var_test3 中被重新赋值为 9,执行var_test4 时,export 给 var_test4 的 y 此时已经是9, 而不是最初的90。

  • 执行脚本的时候,将需要export的变量放在脚本执行命令前面。

    还是上面的例子var_test:

    unset x
    x=99 sh var_test
    # 输出
    99
    100
    
    echo $x
    # 输出, 这里 x 未赋值,默认为 null
    

    将需要export 给subshell 的 x,放在执行命令前,上面的x=99 sh var_test 等价于(x=10;export x;sh var_test), 而(….) 中所做的改变不会更改其parent shell的环境变量。

{….; } VS (…..)

  • {….; }: 命令在当前shell中执行,在{….; } 中做的改变会影响到当前shell.

    例子:

    x=10
    {x=99;}
    echo $x
    # 输出
    99
    
    pwd
    # 输出
    /Users/lucia/Linux/acme
    
    {cd ..; pwd}
    # 输出
    /Users/lucia/Linux
    
    pwd
    # 输出
    /Users/lucia/Linux
    

    如果让脚本在当前shell中执行,还可以使用.source 命令。

    . test.sh
    source test.sh
    
  • (….): 命令在subshell中执行,同样,在(….)做的修改,并不改变当前shell中的环境。

例子:

x=10
(x=99)
echo $x
# 输出
10

pwd
# 输出
/Users/lucia/Linux/acme

(cd ..; pwd)
# 输出
/Users/lucia/Linux

pwd
# 输出
/Users/lucia/Linux/acme

Loop 循环是 subshell

for, while, until 等 loop循环,都属于subshell。

看个例子:

cat subshell_example
# 输出
#!/bin/bash
lineno=0
cat $* |
while read line
do
    lineno=$(($lineno + 1))
done
echo "$lineno"

cat sums
# 输出
9124
-750
3631
1231
-1122

# 执行 subshell_example,参数为sums
sh subshell_example sums
# 输出
0

这里,按照代码逻辑,应该输出sums文件中总行数,也就是5,但由于loop循环是一个subshell,echo "$lineno" 输出的一直是loop 外声明的lineno,也就是0。

那么如何解决?

这里可以使用 herestring解决这个问题,修改subshell_example文件:

cat subshell_example
# 输出
#!/bin/bash
lineno=0

while read line
do
    lineno=$(($lineno + 1))
done <<< "$(cat $*)"

echo "$lineno"

sh subshell_example sums
# 输出
5

参考

《Shell Programming in Unix, Linux and OS X》forth edition

Here strings