zoukankan      html  css  js  c++  java
  • Bash 中的 _ 是不是环境变量

    首先,我们想到的会是 export(等价于 declare -x)命令:

    $ export | grep 'declare -x _='

    没有找到,那么结论就是 _ 不是环境变量?当然没那么简单,否则本篇文章就该结束了。别忘了还有 env(或者 printenv)命令:

    $ env | grep '_='

    _=/usr/bin/env

    这下怎么办,_ 到底是不是环境变量?谁说的对?然而下面还有更诡异的:

    $ bash -c "export | grep 'declare -x _='"

    declare -x _="/bin/bash"

    $ bash -c ":;export | grep 'declare -x _='"

    当用 bash -c 的方式执行且 export 是第一条命令的时候,_ 被认为是环境变量,一旦执行过别的命令,_ 就变成了非环境变量。

    为了找到答案,我只好去翻阅 Bash 源码,下面直接说出我从源码中得出的三点结论:

    1.  Bash 启动的时候

    当 Bash 启动时,如果 Bash 的父进程给 Bash 传入的环境变量数组里有 _,那么 Bash 一定会继承这个环境变量。如果父进程没有传入 _ 环境变量,那么 Bash 会自己创建 _ 这个变量,并把它的初始值赋值为父进程传入的第 0 个参数(argv[0],通常是Bash 的文件名或完整路径),但不会把 _ 设置为环境变量,仅仅是一个普通变量,相关代码在 variables.c 里的 initialize_shell_variables 函数里:

    /* Set up initial value of $_ */
    temp_var = set_if_not ("_", dollar_vars[0]) # 如果用 Bash 代码翻译这句 c 代码就是:[[ -z $_ ]] && _=$0

    2. 在执行任意简单命令之后

    我们常见的 $_ 的用法就是用它来获取上一条执行过的命令的最后一个参数,所以显然在执行完任意一条命令之后,Bash 都得为这个变量重新赋值,不过除了赋值之外,Bash 还做了一件事,就是把 _ 变量标记为非环境变量。在 execute_cmd.c 里有个 bind_lastarg 函数就是来干这两件事的:

    static void
    bind_lastarg (arg)
    char *arg;
    {
    SHELL_VAR *var;

    if (arg == 0)
    arg = "";
    var = bind_variable ("_", arg, 0); # 这句代码是把 _ 的值设置成上次命令的最后一个参数
    VUNSETATTR (var, att_exported); # 这句是把 _ 标记为非环境变量
    }

    3. 执行外部命令之前

    在执行外部命令之前,Bash 会专门把 _ 的值设置成这个外部命令的路径,同时把 _ 标记为环境变量。相关代码在 execute_cmd.c 里的 execute_disk_command 函数中: 

    put_command_name_into_env (command);

    这个 put_command_name_into_env 函数的实现在 variables.c 里:

    void
    put_command_name_into_env (command_name)
    char *command_name;
    {
    update_export_env_inplace ("_=", 2, command_name); # 这句代码翻译成 Bash 代码就是:export _=$command,command 就是外部命令的路径
    }

    三点结论说完了,然后再回头看看刚才那些看似诡异的代码示例。

    为什么 export 看不到 _ 而 env 能看到?

    因为我们执行测试代码通常是在交互式的 Shell 下进行的,交互 Shell 会执行 .bashrc 启动脚本,所以当我们执行 export 命令的时候,一定已经执行过别的命令了,在执行任意命令之后,Bash 都会把 _ 标记为非环境变量,所以 export 看不到 _,我们可以开启一个不执行 rc 脚本的交互 Shell 再看看:

    $ bash --norc

    $ export | grep 'declare -x _=' # bash 也是个外部命令,所以按照上面第 3 点结论里说的,现在这个 Shell 的父 Shell 会把 _ 标记为环境变量,

                                                 # 同时把它的值设置为 bash 的路径,现在这个 Shell 继承了 _ 这个环境变量,所以这里能看到 _

    declare -x _="/bin/bash"

    $ export | grep 'declare -x _=' # 当执行完前一条命令后,按照上面第 2 点结论里说的,当前 Shell 会把 _ 标记为非环境变量,所以这里 _ 已经不是环境变量了

    env 一直能看到,是因为 env 是外部命令,在执行它之前 Bash 总会把 _ 标记为环境变量,同时把 _ 的值设置为 /usr/bin/env。

    为什么连 export _; export  也看不到 _

    虽然 export _ 会把 _ 标记为环境变量,但因为 export _ 也是一条命令,按照上面第二点结论说的,当 export _ 执行后,Bash 又把 _ 标记成了非环境变量。

    在执行 _=foo env 这条命令时,env 命令里看到的 _ 环境变量的值是什么

    是 /usr/bin/env,在执行这条命令时,_ 的值先被赋值成了 foo,然后又被改写成了 /usr/bin/env。

    再通过代码演示证明一下第 1 点结论

    证明一下 Bash 会继承父进程的 _ 环境变量,以及在父进程没有 _ 环境变量的时候 Bash 会在启动时建立这个 _ 变量。

    $ env bash -c 'echo $_' # env 继承了当前 Shell 专门为它设置的 _ 环境变量,然后又传给了它的子进程 Shell

    /usr/bin/env

    $ env _=1 bash -c 'echo $_' # env 重新赋值了 _,然后又传给了 bash

    1

    $ env -i bash -c 'echo $_' # env 在调用 Bash 时没有传入任何环境变量,但 Bash 自己初始化了  _ 变量

    bash

    $ env -i bash -c 'export' # 但 Bash 初始化的 _ 变量不是环境变量,Bash 在启动时只强制创建三个环境变量 

    declare -x OLDPWD
    declare -x PWD="/Users/admin"
    declare -x SHLVL="1"

    manual 里少说了什么

    看一下 manual 里讲 $_ 的段落省略了哪些信息:

    At shell startup, set to the absolute pathname used to invoke the shell or shell script being executed as passed in the environment or argument list

    这里说的只是如果父进程没有传给 Bash _ 环境变量时的表现,如果传了 _,Bash 不会做这件事。

    Subsequently, expands to the last argument to the previous command, after expansion.

    没有说同时会把 _ 标记为非环境变量。

    Also set to the full pathname used to invoke each command executed and placed in the environment exported to that command. 

    没明确指出这里的 command 只指外部命令,虽然 pathname 这个单词已经隐含了它是个外部命令了。 

    总结

    总结一下,这个问题的答案就是:_ 在 Bash 刚刚启动的时候(执行第一条命令之前)可能是环境变量(来自父进程)或者在执行外部命令之前的那一刻是环境变量,在其他时候都是非环境变量。

    为什么

    难道你没有疑问吗,为什么要特意的在执行完一条简单命令后,就把 _ 变成非环境变量,从 Bash 启动时就让它一直保持环境变量不就得了,干嘛切来切去。为此我特地问了 Bash 现任作者,他的猜测(当时不是他维护 Bash)和我预想的一样:那就是因为没什么用,你想想看,如果你执行的是个内部命令,内部命令本来就运行在当前 Shell 里,即便不是环境变量,它也能访问到,如果你执行的是个外部命令,_ 本来就会被改写成环境变量(同时改值),所以当时写 Bash 的人就写了那么一句,没有什么特别的考虑。

  • 相关阅读:
    URAL 1658. Sum of Digits(DP)
    HDU 4433 locker(SPFA+DP)
    Vijos 1092 全排列
    POJ 1141 Brackets Sequence(DP)
    HDU 4597 Play Game
    HDU 1693 Eat the Trees(插头DP)
    USACO 5.4 Twofive(DP)
    USACO 5.5 Hidden Password(搜索+优化)
    USACO 5.5 Picture(周长并)
    USACO 5.4 Telecowmunication(最大流+枚举)
  • 原文地址:https://www.cnblogs.com/ziyunfei/p/4787089.html
Copyright © 2011-2022 走看看