.bashrcでexportしたPATHが/procにあるプロセスファイルの環境変数(PATH)と一致しないように見える


`Ubunt 15.04`を使っています。  
どういう際に利用するかは措いておくとして、`/proc`にある各プロセスIDの名前がついたディレクトリにある、`environ`というファイルを、プログラミング時の環境変数取得に利用できないかと思いました。

そこで、例えば、gnome端末エミュレータから起動したシェルの`PATH`であれば次のように取得できることを確認しました。

[code]

    $echo $$
    3009
    $ tr '\000' '\012' < /proc/3009/environ | grep ^PATH
    PATH=/home/cul8er/bin:/home/cul8er/.gem/ruby/2.1.0/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
    
[/code]

念の為、実際に`PATH`を`echo`で表示させました。

[code]

    $ echo $PATH
    /home/cul8er/jdk1.8.0_60/bin:/home/cul8er/bin:/home/cul8er/.gem/ruby/2.1.0/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
    
[/code]

`/home/cul8er/jdk1.8.0_60/bin`が加わっています。つまり、`/proc`の情報と異なっています。  
調べると、過去にnetbeansを使うためにインストールしたjavaを使えるよう`.bashrc`で`PATH`を設定していました。

[code]

    -- /home/cul8er/.bashrc --
    export JAVA_HOME=$HOME/jdk1.8.0_60
    export PATH=$JAVA_HOME/bin:$PATH
    
[/code]

`.profile`でもrubyを実行するために`PATH`情報の変更がされていました。(なんでこういう違いを作ったのかは思いだせません…)

[code]

    -- /home/cul8er/.profile
    # if running bash
    if [ -n "$BASH_VERSION" ]; then
      # include .bashrc if it exists
      if [ -f "$HOME/.bashrc" ]; then
        . "$HOME/.bashrc"
      fi
    fi
    
    # set PATH so it includes user's private bin if it exists
    if [ -d "$HOME/bin" ] ; then
      PATH="$HOME/bin:$HOME/.gem/ruby/2.1.0/bin:$PATH"
    fi
    
[/code]

`.profile`内で`.`コマンドを使って`.bashrc`を呼び出している点に加えて、通例言われているところであれば、`.profile`もログインシェルから`.`コマンドで実行されるはずなので、`.bashrc`で変数を`export`すれば問題なくログインシェルの環境変数として使えるはずですし、実際`echo`の表示がそれを裏付けているように思えます。

カレントシェルがログインシェルでなくてもサブシェルなら`.bashrc`は起動されるはずですからなおさらです。

それが`/proc`以下の`environ`ファイルでは`.profile`での設定しか含まれていません。

`man proc`でオンラインヘルプを読むと以下の記述しか見つけられませんでした。

> /proc/[pid]/environ このファイルはプロセスの環境変数を含んでいる。 各エントリはヌルバイト ('\0')  
>  で区切られていて、 末尾にヌルバイトがあるかもしれない。

こういうものだと現実から理解するのも悪くないですが、単純に自分が無知なままでプログラミングすると後に無用な苦労をすることも多々ありますので、`.bashrc`での設定が生かされない理由を、分かるものなら知りたいです。  
ヒントでもいいのでご教示おねがいします。




結論から言いますと、bash の場合は environ 変数を直接には使っていません(少なくとも Linux OS では)。代わりに `char
**shell_environment` と `char **export_env` というグローバル変数を使っています。

**bash-4.3/shell.c**

[code]

    /* The environment that the shell passes to other commands. */
    char **shell_environment;
               :
    #if defined (NO_MAIN_ENV_ARG)
    extern char **environ;  /* used if no third argument to main() */
    #endif
               :
    int
    main (argc, argv, env)
         int argc;
         char **argv, **env;
               :
    shell_environment = env;
    
[/code]

**bash-4.3/variable.c**

[code]

    /* An array which is passed to commands as their environment.  It is
       manufactured from the union of the initial environment and the
       shell variables that are marked for export. */
    char **export_env = (char **)NULL;
    
[/code]

bash プロセスの開始時に `shell_environment` 変数(の内容)から `export_env` 変数(の内容)を作成しています。その後は
bash 内部で export 関数を実行すると、`export_env` 変数のみが変更されます。

gdb で bash プロセスの内部を調べてみます。

[code]

    $ echo $$
    11013
    $ echo $PATH
    /usr/local/go/bin:/usr/local/bin:/usr/bin:...
    $ export PATH=/usr/games:$PATH; echo $PATH
    /usr/games:/usr/local/go/bin:/usr/local/bin:/usr/bin:...
    $ cat /proc/$$/environ | grep -z ^PATH
    PATH=/usr/local/go/bin:/usr/local/bin:/usr/bin:...
    
    $ sudo bash -c 'echo 0 > /proc/sys/kernel/yama/ptrace_scope'
    $ gdb -p $$
    (gdb) p *environ
    Cannot access memory at address 0x0
    
    (gdb) call strlen((char *)shell_environment)
    $24 = 244
    (gdb) p $24 / sizeof(char *)
    $25 = 61
    (gdb) p ((char **)shell_environment)[0]
    $41 = 0xbff63956 "BC_ENV_ARGS=-l /home/nemo/.bc"
    (gdb) p ((char **)shell_environment)[$25-1]
    $42 = 0xbff63fe7 "TERM=xterm"
    (gdb) find $41,$42,'\0','P','A','T','H'
    0xbff63c17
    1 pattern found.
    (gdb) p $_+1
    $44 = (void *) 0xbff63c18
    (gdb) x/s $44
    0xbff63c18: "PATH=/usr/local/go/bin:/usr/local/bin:..."
    
    (gdb) p ((char **)export_env)[24]
    $50 = 0x8e76408 "PATH=/usr/games:/usr/local/go/bin:/usr/local/bin:..."
    
[/code]

ここで、`shell_environment` 内の PATH 環境変数を変更してみましょう。

[code]

    (gdb) set {char}($44+22) = 0
    (gdb) x/s $44
    0xbff63c18: "PATH=/usr/local/go/bin"
    
    $ cat /proc/11013/environ | grep -z ^PATH
    PATH=/usr/local/go/bin
    
[/code]

元に戻します。

[code]

    (gdb) set {char}($44+22) = ':'
    (gdb) x/s $44
    0xbff63c18: "PATH=/usr/local/go/bin:/usr/local/bin:..."
    
    $ cat /proc/11013/environ | grep -z ^PATH
    PATH=/usr/local/go/bin:/usr/local/bin:/usr/bin:...
    
[/code]

`shell_environment` の実体は `environ` なので当然ではありますけれども。

参考情報: [Linux/fs/proc/base.c:environ_read()](http://lxr.free-
electrons.com/source/fs/proc/base.c?v=4.2#L951)

**追記**

余談ですが、要は対象の bash プロセスを exec してしまえば良いので、以下の様な方法で `/proc/PID/environ`
を更新(?)することもできるでしょう(さしたる意味はないのですが…)。

[code]

    $ echo $$
    15231
    $ echo $PATH
    /usr/local/go/bin:/usr/local/bin:/usr/bin:...
    $ export PATH=/usr/games:$PATH; echo $PATH
    /usr/games:/usr/local/go/bin:/usr/local/bin:/usr/bin:...
    $ cat /proc/$$/environ | grep -z ^PATH
    PATH=/usr/local/go/bin:/usr/local/bin:/usr/bin:...
    
    ## Bash's exec function
    $ exec /proc/$$/exe
    $ cat /proc/15231/environ | grep -z ^PATH
    PATH=/usr/games:/usr/local/go/bin:/usr/local/bin:/usr/bin:...
    
    ## Call execvpe in GDB
    $ gdb -p 15231
    (gdb) call execvpe("/bin/bash", (char **)0, export_env)
    process 15231 is executing new program: /bin/bash
    
    $ cat /proc/15231/environ | grep -z ^PATH
    PATH=/usr/games:/usr/local/go/bin:/usr/local/bin:/usr/bin:...
    
[/code]




結論から言いますと、bash の場合は environ 変数を直接には使っていません(少なくとも Linux OS では)。代わりに `char
**shell_environment` と `char **export_env` というグローバル変数を使っています。

**bash-4.3/shell.c**

[code]

    /* The environment that the shell passes to other commands. */
    char **shell_environment;
               :
    #if defined (NO_MAIN_ENV_ARG)
    extern char **environ;  /* used if no third argument to main() */
    #endif
               :
    int
    main (argc, argv, env)
         int argc;
         char **argv, **env;
               :
    shell_environment = env;
    
[/code]

**bash-4.3/variable.c**

[code]

    /* An array which is passed to commands as their environment.  It is
       manufactured from the union of the initial environment and the
       shell variables that are marked for export. */
    char **export_env = (char **)NULL;
    
[/code]

bash プロセスの開始時に `shell_environment` 変数(の内容)から `export_env` 変数(の内容)を作成しています。その後は
bash 内部で export 関数を実行すると、`export_env` 変数のみが変更されます。

gdb で bash プロセスの内部を調べてみます。

[code]

    $ echo $$
    11013
    $ echo $PATH
    /usr/local/go/bin:/usr/local/bin:/usr/bin:...
    $ export PATH=/usr/games:$PATH; echo $PATH
    /usr/games:/usr/local/go/bin:/usr/local/bin:/usr/bin:...
    $ cat /proc/$$/environ | grep -z ^PATH
    PATH=/usr/local/go/bin:/usr/local/bin:/usr/bin:...
    
    $ sudo bash -c 'echo 0 > /proc/sys/kernel/yama/ptrace_scope'
    $ gdb -p $$
    (gdb) p *environ
    Cannot access memory at address 0x0
    
    (gdb) call strlen((char *)shell_environment)
    $24 = 244
    (gdb) p $24 / sizeof(char *)
    $25 = 61
    (gdb) p ((char **)shell_environment)[0]
    $41 = 0xbff63956 "BC_ENV_ARGS=-l /home/nemo/.bc"
    (gdb) p ((char **)shell_environment)[$25-1]
    $42 = 0xbff63fe7 "TERM=xterm"
    (gdb) find $41,$42,'\0','P','A','T','H'
    0xbff63c17
    1 pattern found.
    (gdb) p $_+1
    $44 = (void *) 0xbff63c18
    (gdb) x/s $44
    0xbff63c18: "PATH=/usr/local/go/bin:/usr/local/bin:..."
    
    (gdb) p ((char **)export_env)[24]
    $50 = 0x8e76408 "PATH=/usr/games:/usr/local/go/bin:/usr/local/bin:..."
    
[/code]

ここで、`shell_environment` 内の PATH 環境変数を変更してみましょう。

[code]

    (gdb) set {char}($44+22) = 0
    (gdb) x/s $44
    0xbff63c18: "PATH=/usr/local/go/bin"
    
    $ cat /proc/11013/environ | grep -z ^PATH
    PATH=/usr/local/go/bin
    
[/code]

元に戻します。

[code]

    (gdb) set {char}($44+22) = ':'
    (gdb) x/s $44
    0xbff63c18: "PATH=/usr/local/go/bin:/usr/local/bin:..."
    
    $ cat /proc/11013/environ | grep -z ^PATH
    PATH=/usr/local/go/bin:/usr/local/bin:/usr/bin:...
    
[/code]

`shell_environment` の実体は `environ` なので当然ではありますけれども。

参考情報: [Linux/fs/proc/base.c:environ_read()](http://lxr.free-
electrons.com/source/fs/proc/base.c?v=4.2#L951)

**追記**

余談ですが、要は対象の bash プロセスを exec してしまえば良いので、以下の様な方法で `/proc/PID/environ`
を更新(?)することもできるでしょう(さしたる意味はないのですが…)。

[code]

    $ echo $$
    15231
    $ echo $PATH
    /usr/local/go/bin:/usr/local/bin:/usr/bin:...
    $ export PATH=/usr/games:$PATH; echo $PATH
    /usr/games:/usr/local/go/bin:/usr/local/bin:/usr/bin:...
    $ cat /proc/$$/environ | grep -z ^PATH
    PATH=/usr/local/go/bin:/usr/local/bin:/usr/bin:...
    
    ## Bash's exec function
    $ exec /proc/$$/exe
    $ cat /proc/15231/environ | grep -z ^PATH
    PATH=/usr/games:/usr/local/go/bin:/usr/local/bin:/usr/bin:...
    
    ## Call execvpe in GDB
    $ gdb -p 15231
    (gdb) call execvpe("/bin/bash", (char **)0, export_env)
    process 15231 is executing new program: /bin/bash
    
    $ cat /proc/15231/environ | grep -z ^PATH
    PATH=/usr/games:/usr/local/go/bin:/usr/local/bin:/usr/bin:...
    
[/code]

