得之我幸 失之我命

when someone abandons you,it is him that gets loss because he lost someone who truly loves him but you just lost one who doesn’t love you.

bashrc 里修改环境变量要加 export 吗

老问题重提,很早之前就遗留下了这个问题,在 .bashrc 中修改环境变量的时候需要 export 吗?当我设置一个变量在脚本中,直接将它 source 以后,可以发现这个变量就可以直接可以用了,当一个 bash 启动的时候,是会加载 .bashrc 的(这里的说法可能不那么严谨,但在 GUI 打开一个终端窗口时且默认的 shell 是 bash 时,那么它是会加载 .bashrc 的),那么在这个文件中的变量包括环境变量其实是直接可以生效的,问题来了,为什么在这个文件里定义环境变量的时候还需要加 export

从对问题的描述中可以拆解成好几个小问题,一是 shell 变量和环境变量的区别,二是子进程和子 shell 的区别,三是 bash 启动的时候是如何选择性的加载文件,最后一点值得单独谈一谈,所以先说前两个点

shell 变量和环境变量的区别

这其实是变量作用域的区别,也就是变量的有效可以使用的范围,换言之,这儿的区别在于这个变量的作用范围不同

  1. 作用范围哪儿不同

    shell 变量是当前 shell 私有的,不会被它的子进程所访问到,即 shell 变量的作用范围是当前 shell

    环境变量是可以被当前 shell 的子进程访问的,即环境变量的作用范围是当前 shell 和它的子进程

  2. 为什么需要 export

    Bourne shell 系列并不真正区分 shell 变量和环境变量,当 shell 启动时,它读取环境变量表中的信息,使用相同的名称(按照惯例也是大写)为每个变量定义自己的 shell 变量,并复制值,从那时起,shell 只引用它的 shell 变量,如果对 shell 变量进行了更改,则必须将其显式的 export 到相应的环境变量,以便任何派生的子进程都能看到更改

这儿就引发了一个小问题:子 shell 和子进程是不是一回事?

子进程和子 shell 的区别

先说结论,是也不是,子进程包含了子 shell,用于区别是否为子 shell 的一个点在于创建子进程以后是否调用了 exec

  1. 当前 shell 进程因命令行中使用了某些特殊语法而产生的子 shell

    • 小括号,也叫组命令 (cmd1;cmd2)
    • 管道 cmd1|cmd2
    • 命令替换 $(cmd)

    上述进入子 shell 的方式,在 fork 产生子 bash 进程后不会执行 exec

    fork 本质是复制进程,复制当前进程做为一个副本,而后将这些资源交给子进程,因此子进程会继承父进程的一些资源,如环境变量、变量等

    • 进程替换 <(cmd),>(cmd)

    注意,“进程替换”看起来好像产生了一个子 shell,其实只是一个障眼法而已,进程替换只是借助文件在 () 内部和外部的命令之间传递数据,但是它并没有创建子 shell,换言之,() 内部和外部的命令是在一个进程(也就是当前进程)中执行的

  2. 显式调用 bash 程序产生的子进程

    • bash -c ‘cmd’
    • bash 命令
    • shell 脚本

    上述进入子进程的方式,在刚 fork 产生子 bash 进程时也会继承父 shell 资源,但随后的 exec 调用 bash 程序会替换覆盖掉子 bash 进程,所以这种方式只会保留环境变量

    exec 的本质是加载另外一个程序来代替当前运行的进程,即在不创建新进程的情况下去加载一个新程序,需要注意的是,在进程执行完成后即使存在未执行的内容也会直接退出 exec 所在的 shell 环境

  3. 为了更好的判断子 shell 和子进程,Bash 提供了两个环境变量 —— SHLVL 和 BASH_SUBSHELL

    SHLVL 是记录多个 Bash 进程实例嵌套深度的累加器,每次进入一层普通的子进程,SHLVL 的值就加 1;而 BASH_SUBSHELL 是记录一个 Bash 进程实例中多个子 Shell(sub shell)嵌套深度的累加器,每次进入一层子 Shell,BASH_SUBSHELL 的值就加 1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    # 一个 test.sh
    #!/bin/bash
    echo "$SHLVL $BASH_SUBSHELL"

    $ echo "$SHLVL $BASH_SUBSHELL" # 直接在 bash 中输出
    2 0

    # 执行 bash 命令开启一个新的 shell 会话
    $ bash
    $ echo "$SHLVL $BASH_SUBSHELL" # 直接在 bash 中输出
    3 0
    $ bash ./test.sh # 通过 bash 命令运行脚本
    4 0
    $ echo "$SHLVL $BASH_SUBSHELL" # 脚本结束后直接在 bash 中输出
    3 0
    $ ./test.sh # 直接运行脚本
    4 0
    $ echo "$SHLVL $BASH_SUBSHELL" # 脚本结束后直接在 bash 中输出
    3 0
    $ exit

    # 回到外层 bash
    $ echo "$SHLVL $BASH_SUBSHELL" # 直接在 bash 中输出
    2 0
    $ bash -c "echo '$SHLVL $BASH_SUBSHELL'" # bash -c 输出
    2 0

    $ (echo "$SHLVL $BASH_SUBSHELL") # 组命令
    2 1
    $ echo "hello" | { echo "$SHLVL $BASH_SUBSHELL"; } # 管道
    2 1
    $ var=$(echo "$SHLVL $BASH_SUBSHELL") # 命令替换
    $ echo $var
    2 1
    $ echo "hello" > >(echo "$SHLVL $BASH_SUBSHELL") # 进程替换
    2 0
    $ read < <(echo "$SHLVL $BASH_SUBSHELL") # 进程替换
    $ echo $REPLY
    2 0

至此,定义环境变量的时候为什么需要加 export 结

be slow to promise and quick to perform.