20039


`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`での設定が生かされない理由を、分かるものなら知りたいです。  
ヒントでもいいのでご教示おねがいします。


CC BY-SA 3.0
2015-12-14T22:02:22.163
19984
2015-12-22T07:08:24.057
2015-12-14T23:48:52.817
9403
9403
question
linux bash ubuntu
.bashrcでexportしたPATHが/procにあるプロセスファイルの環境変数(PATH)と一致しないように見える


/proc/PID/environ は、そのプロセスが起動したときの環境変数が保持されているのだと思います。  
bash の場合、起動後に .bashrc や /etc/profile.d/*.sh などで PATH が変わっても、/proc/PID/environ
の値は変わらないのではないかと。


CC BY-SA 3.0
2015-12-15T00:23:41.260
19989
2015-12-15T00:23:41.260
4603
19984
answer


（fumiyasさんへのコメントにしようとしたら、reputationが低くてコメントできないので、回答で…）  
親から渡ってきた環境変数は、スタックの先にあってプロセスから参照できるし、カーネルからも見えて、それをカーネルは /proc/$$/environ
として見せているわけですが、  
子プロセスに渡す環境変数は、execve(2)システムコールの第３引数で渡す文字列配列に過ぎないわけで、カーネルから見ると、プロセスのメモリ空間にある文字列配列のどれが環境変数なのかは分からないからですね。  
普通はグローバル変数 char **environ;
の指す先がそれなので、実行ファイルのシンボルテーブル調べてその先を見るとかすれば分かることが多いわけですが（でも execve(2)
に渡す引数がこれだという保証はない）、カーネルはそこまでは調べません。


CC BY-SA 3.0
2015-12-15T06:29:25.340
20012
2015-12-15T06:29:25.340
13678
19984
answer


有益情報かどうかわかりませんが。

<http://mattn.kaoriya.net/software/linux/20151215205348.htm>

gdb を起動するので環境によっては管理者権限が必要です。


CC BY-SA 3.0
2015-12-15T12:00:07.333
20025
2015-12-15T12:00:07.333
440
19984
answer


結論から言いますと、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]


CC BY-SA 3.0
2015-12-15T15:35:12.143
20039
2015-12-15T18:21:50.780
2015-12-15T18:21:50.780
19984
answer


すでに完全な回答が示されましたが、それを補足する証拠を後学のために残しておきます。  
今手元で`ps axlf`を実行し、プロセスのツリー階層を調べたところ以下の構造になっておりました。(無関係な部分は省いて抜粋してあります)

[code]

    992  /usr/sbin/lightdm
    1356   \_ lightdm --session-child 12 19
    1557       \_ /sbin/upstart --user
    2199            \_ /usr/lib/gnome-terminal/gnome-terminal-server --app-id
    2921                 \_ bash
    
[/code]

左端の数値がプロセスIDです。  
これらを上から順に調べると、  
`lightdm`が起動した時点での`PATH`は`/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin`でした。ログイン前なのでこれは当然ですよね。  
次に`lightdm --session-child 12 19`が起動するときも`PATH`に変更はありません。

これが`/sbin/upstart
--user`を実行した時点で`/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`と変わっていました。  
つまり、`upstart`が`exec`された時点では既にログインが完了しており、`lightdm --session-child 12
19`起動後に`.profile`の実行があったことが示されています。

`.profile`が実行されたとき、呼び出し元で`BASH_VERSION`を設定しさらに`.`コマンドで呼び出されなければ`.bashrc`が実行されないことは`.profile`の内容から明らかですが、すでに見ている結果から考えるとこの時点で`BASH_VERSION`の設定はなかったものと考えられます。

一応`lightdm`(ver.1.7.12)のソースコードを調べてみましたが、`BASH_VERSION`という記述そのものがありませんでした。  
C言語で書かれており、`.profile`を実行するのに相当する箇所を探してもみましたが力不足で分かりませんでした。  
基本的には`lightdm.c`の1205行目に

[code]

    return session_child_run (argc, argv);
    
[/code]

という記述があり、これが結果的に`/sbin/upstart --user`というプロセスの起点担っています。  
`session_child_run()`自体の定義は`session-child.c`というファイルに記載があって、`PAM`を用いた認証後に

[code]

    execve (command_argv[0], command_argv, pam_getenvlist (pam_handle)); (700行目)
    
[/code]

で子プロセス(`/sbin/upstart`)の起動につなげているようです。  
（詳しいところまで調べきれなかったので間違っていたらすみません。もし分かる方がいらしたらご指摘おねがいします。）

こうした経緯の後、最後にgnome端末エミュレータを介してプロセスID`2921`のサブシェルが起動されています。  
このときに`.bashrc`が読み込まれ、質問時の状況が生まれたと理解できました。

* * *

**追記**  
`BASH_VERSION`の部分に関して解釈の間違いを指摘いただきまして、修正およびその部分の調査結果を追加しました。(12/17)


CC BY-SA 3.0
2015-12-15T19:50:43.997
20041
2015-12-16T20:28:51.393
2015-12-16T20:28:51.393
9403
9403
19984
answer


質問についているコメントを見ていただくとわかりますが、`/proc/id/environ`
の内容はそのプロセスが作成されたときの情報であるのに、`/proc/self/environ`の内容はまさに今のカレントプロセスの情報となっていることに関して、それの理由についての言及がないままとなっていました  
[Unix&Linux](http://unix.stackexchange.com)という、StackExchange系のサイトに情報があったので後学のために紹介して終わりにしようと思います。  
[How is /proc/self implemented in
Linux?](https://unix.stackexchange.com/questions/34192/how-is-proc-self-
implemented-in-linux)

答え1

> The proc filesystem is entirely virtual, and is implemented so the  
>  internal VFS readlink delegates to the right place for special  
>  symlinks. So, it calculates what self points to when it is read /  
>  traversed, not every context switch.

答え2

> Files in /proc are not stored on a disk, they are generated on the fly  
>  by the kernel. (中略) The contents of the /proc/self symbolic link is  
>  generated by a function that fills a buffer with the pid of the  
>  calling process.

厳密な訳は難しいので概要を書きますと、`Procfs`(`Proc`ファイルシステム)自体が仮想のファイルシステムであり、`/proc/self`リンクが指すディレクトリの内容はリンクがアクセスされるたびにカーネルレベルで逐一作成されるということだそうです。  
この説明だと異なるユーザーが/proc/selfを同時にアクセスしたらどうなるのか興味のあるところですが、<https://www.kernel.org/doc/Documentation/filesystems/proc.txt>によれば

> The link self points to the process reading the file system.

`self`リンクは`Procfs`を読もうとするプロセスそのものを指すと書いているので、仮想的に全ユーザーにそのように見えるものを提供しているみたいです。  
※正直に言うと、これの検証の仕方が分かりませんorz

事実に反する点などありましたらご指摘お願いします。


CC BY-SA 3.0
2015-12-22T07:08:24.057
20283
2015-12-22T07:08:24.057
2017-04-13T12:37:20.867
-1
9403
19984
answer