V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
autumn2018
V2EX  ›  程序员

Bash 默认把 PATH 传给子 bash,请问是出于什么考虑?

  •  
  •   autumn2018 · 2018-11-30 21:49:32 +08:00 · 4697 次点击
    这是一个创建于 2183 天前的主题,其中的信息可能已经有所发展或是发生改变。

    即使不 export,bash 里再开的 bash(子 bash),也能继承到 PATH 这个 shell 变量,请问,这在平常的脚本编程中有什么用途?不然,bash 为什么这样设计呢?

    第 1 条附言  ·  2018-12-01 04:43:12 +08:00
    可能我表述的不明白.大家都在说"环境变量"的问题.
    PATH 在 bash 里本质上还是一个普通的 shell 变量,需要 export 才能生效. export 应该就是一个 setenv()的过程.
    但是 PATH 变量又稍微有些特殊,你在 bash 里定义一个 foo 变量,只要你不 export foo,子 bash 是不继承这个 foo 的,但 PATH 变量,不管你 export 与否,子 bash 都会继承.
    第 2 条附言  ·  2018-12-01 16:34:30 +08:00
    感谢提醒,补充在 bash 上测试的试验,这个 bash 是通过-norc 运行起来的,"子 bash"也是通过-norc 运行的.

    bash-4.3$ a=1
    bash-4.3$ PATH=$PATH:xxx
    bash-4.3$ echo $PATH
    /usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:xxx
    bash-4.3$ echo $a
    1
    bash-4.3$ bash -norc
    bash-4.3$ echo $PATH
    /usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:xxx
    bash-4.3$ echo $a

    bash-4.3$

    可以看到 PATH 被传递给子 shell 了,但变量 a 没有.
    33 条回复    2018-12-10 10:36:42 +08:00
    neoblackcap
        1
    neoblackcap  
       2018-11-30 22:09:32 +08:00 via iPhone
    不是 bash 是这样设计,是 unix 环境下,子进程默认继承父进程的环境变量
    flynaj
        2
    flynaj  
       2018-11-30 22:39:18 +08:00 via Android
    不止 Linux,Windows 也会传环境变量到子进程
    wd
        3
    wd  
       2018-11-30 22:44:31 +08:00 via iPhone
    这不是环境变量么,那在这个环境执行的命令不就会继承么?
    WordTian
        4
    WordTian  
       2018-11-30 22:45:57 +08:00 via Android
    要是 path 不继承的话,那子 bash 就没有什么命令可用了
    undeflife
        5
    undeflife  
       2018-11-30 23:13:20 +08:00
    bash shell 不同状态读取的配置文件优先级

    https://www.solipsys.co.uk/images/BashStartupFiles1.png
    widewing
        6
    widewing  
       2018-12-01 02:37:02 +08:00 via Android
    ???难道其他变量没有被继承吗?
    binux
        7
    binux  
       2018-12-01 05:05:34 +08:00
    PATH 是环境变量不是 shell 变量啊
    dangyuluo
        8
    dangyuluo  
       2018-12-01 08:31:50 +08:00
    Quote form Wikipedia:

    PATH is an environment variable on Unix-like operating systems, DOS, OS/2, and Microsoft Windows,
    TonyLiu2ca
        9
    TonyLiu2ca  
       2018-12-01 08:49:27 +08:00
    man bash
    whileFalse
        10
    whileFalse  
       2018-12-01 08:59:16 +08:00
    没有 PATH 跑不起来好吗。
    gowa2017
        11
    gowa2017  
       2018-12-01 09:01:10 +08:00 via iPhone
    loginshell fork child bash 采用了 cow 技术 刚开始的时候是共享内存空间的
    iwtbauh
        12
    iwtbauh  
       2018-12-01 11:33:04 +08:00 via Android
    并没有传给子进程

    只是新的 bash 初始化时读取了配置文件然后自己创建了 PATH 变量

    请 lz 尝试:

    unset PATH
    PATH="foo"
    /bin/bash
    echo "$PATH"
    lululau
        13
    lululau  
       2018-12-01 11:39:33 +08:00
    只听说过子进程和子 Shell,子 Bash 是什么鬼
    weyou
        14
    weyou  
       2018-12-01 11:53:36 +08:00 via Android
    系统启动的时候 PATH 已经是环境变量了,你对它做出的任何修改它还是一个环境变量,所以都会被子进程继承。这有啥奇怪的呢。
    chinvo
        15
    chinvo  
       2018-12-01 12:03:48 +08:00 via iPhone
    你把 foo 塞进系统环境变量里,“不 export ”也能继承。所有已经是环境变量的变量,包括 path、lc_* 等,都是这样的
    cnt2ex
        16
    cnt2ex  
       2018-12-01 12:47:34 +08:00
    环境变量的传递不是 bash 完成的,是操作系统完成的。在创建子进程的时候操作系统把带有环境变量的那部分内存区域拷贝到子进程的内存完成了环境变量的传递。
    momocraft
        17
    momocraft  
       2018-12-01 12:57:09 +08:00
    先讲清楚你的"子 bash" 是什么东西, 比如附上你用的命令, 我们才能继续讨论环境变量是如何初始化的
    DinoStray
        18
    DinoStray  
       2018-12-01 12:57:29 +08:00 via iPhone
    @chinvo 第一次知道还有这个区别,那怎样算放入系统环境变量呢?要放在哪个文件里?
    ihainan
        19
    ihainan  
       2018-12-01 13:07:04 +08:00
    还真是这样,这个我都没注意过,习惯性都是加上 export。
    rrfeng
        20
    rrfeng  
       2018-12-01 13:27:55 +08:00
    楼主的总结恰好是反的

    环境变量会传递给子进程,但是 PATH 恰恰是一个很可能子进程(特别是 bash )会自己重载的环境变量
    jdhao
        21
    jdhao  
       2018-12-01 13:47:11 +08:00 via Android
    @momocraft 正解,这一群回答的人基本都是在乱讲,没几个说到点子的
    weyou
        22
    weyou  
       2018-12-01 15:08:30 +08:00 via Android
    @jdhao 他都没解,哪里来的“正解”?

    “子 bash ”这个叫法虽然很奇怪,但一看就知道是一个 bash 里调用的另一个 bash 啊
    jdhao
        23
    jdhao  
       2018-12-01 15:44:21 +08:00 via Android
    @weyou 怎么调的?用了什么命令?原文啥都没说
    autumn2018
        24
    autumn2018  
    OP
       2018-12-01 16:29:30 +08:00
    @jdhao
    bash-4.3$ a=1
    bash-4.3$ PATH=$PATH:xxx
    bash-4.3$ echo $PATH
    /usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:xxx
    bash-4.3$ echo $a
    1
    bash-4.3$ bash -norc
    bash-4.3$ echo $PATH
    /usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:xxx
    bash-4.3$ echo $a

    bash-4.3$
    no1xsyzy
        25
    no1xsyzy  
       2018-12-01 17:04:17 +08:00
    重点不是传递,而是设置 PATH 会直接修改当前环境变量

    no1xsyzy@XSY-New:~$ PATH=foo
    no1xsyzy@XSY-New:~$ bash
    Command 'bash' is available in '/bin/bash'
    The command could not be located because '/bin' is not included in the PATH environment variable.
    bash: command not found

    而且其他的只要 export 过后修改也会改变环境变量

    no1xsyzy@XSY-New:~$ export a=1
    no1xsyzy@XSY-New:~$ a=2
    no1xsyzy@XSY-New:~$ /usr/bin/env | /bin/grep "^a="
    a=2

    所以应该是和 @chinvo #15 说的一样,一个变量一但和环境产生关联就会一直绑定。
    no1xsyzy
        26
    no1xsyzy  
       2018-12-01 17:19:15 +08:00
    help export
    help declare | grep -- -x

    export 不是设置环境变量,而是设置变量类型为导出类型,即这个变量的类型是个环境变量。
    export foo=bar 只是个语法糖。
    先 export -n PATH 这一现象就会消失。

    no1xsyzy@XSY-New:~$ export -n PATH
    no1xsyzy@XSY-New:~$ PATH=foo
    no1xsyzy@XSY-New:~$ bash
    Command 'bash' is available in '/bin/bash'
    The command could not be located because '/bin' is not included in the PATH environment variable.
    bash: command not found
    no1xsyzy@XSY-New:~$ /bin/bash -c 'echo $PATH'
    /usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:.
    iwtbauh
        27
    iwtbauh  
       2018-12-01 19:27:00 +08:00 via Android
    @autumn2018 #24

    你并没有在修改 PATH 之前 unset 它,PATH 是环境变量当然会传递给子进程
    weyou
        28
    weyou  
       2018-12-01 23:21:44 +08:00 via Android
    @jdhao 不管怎么调的,调用了什么,这个问题的答案都是一样的。一个环境变量只要没有被 unset,都会被所有的子进程(本贴中子 bash 也是子进程)继承。就算被修改了也还是环境变量依然会被继承。
    xiaoshenke
        29
    xiaoshenke  
       2018-12-02 00:18:42 +08:00
    因为是环境变量啊 bash 自然是做了特殊处理了
    julyclyde
        30
    julyclyde  
       2018-12-02 10:34:55 +08:00   ❤️ 2
    先说结论:
    因为 PATH 变量是从上一层继承过来的,默认就是环境变量,所以在 shell 这一层不需要通过 export 命令让它成为环境变量

    再说证据:
    http://git.savannah.gnu.org/cgit/bash.git/tree/execute_cmd.c 的 execute_disk_command 函数里执行了
    exit (shell_execve (command, args, export_env));
    其中 export_env 是从 variables.c 里边来的,被 execute_disk_command 调用 shell_execve 之前稍早几句的 maybe_make_export_env (); 写入内容的一个数组

    再看 variables.c 文件,maybe_make_export_env 调用了 make_var_export_array,后者又调用了 export_environment_candidate,而最后这个函数只有一句话:
    return (exported_p (var) && (invisible_p (var) == 0 || imported_p (var)));
    关键在于 imported_p。这个 imported_p 并不是个函数而是一个宏,在 variables.h 里定义的,判断条件里的 att_imported 吸引了我。再搜 att_imported,发现 initialize_shell_variables 函数执行的时候会对部分变量标记 att_imported ( att 即 attribute )

    最后说值的继承关系:
    在 systemd 为基础的 Linux 发行版里,systemd-exec(5)准备了固定值 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
    检查 /proc/pid/environ 可以发现,sshd 和 getty 的 PATH 环境变量值均于此一致
    julyclyde
        31
    julyclyde  
       2018-12-02 10:35:41 +08:00
    看了看前边的回答,觉得有不少人理解能力有问题
    Kobayashi
        32
    Kobayashi  
       2018-12-05 17:32:02 +08:00 via Android
    没有 PATH 变量,外部程序去哪里调用……
    momocraft
        33
    momocraft  
       2018-12-10 10:36:42 +08:00
    我又看了一下,PATH 可能是默认就 export 的环境变量 (我机器上用没有.bashrc/.bashprofile 的用户 declare -p PATH 也会看到-x)。

    还没有找到这个 export 是在哪个级别决定的 (bash 固定,或哪个全局配置文件)。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3527 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 04:24 · PVG 12:24 · LAX 20:24 · JFK 23:24
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.