Linuxでシェル間の設定の引継ぎを蛇足感満載で確認した

# この記事の内容
前回書いた「 [.bash_profileと.bashrcなんて使い分けなくてよかったんや！](https://qiita.com/dark-space/items/cf25001f89c41341a9fd)」 という記事では説明しなかった、
`.bash_profile`や`.bashrc`に書いた設定が適用される範囲などについて書いています。
もともと前回のと合わせて一つの記事にしようと考えていたのですが、
内容として独立させられると思ったのと、量が多すぎるかと思ったので分けました。
姉妹記事的な立ち位置なので、両方読んで頂けると理解が深まるかと思います。
（「別の記事を読まないと成り立たない記事」というのは書きたくないのですが、
もともと１つの記事にしようと思っていたので、かなり依存してしまっています。。。）

調べる前は、`bash hoge.sh`とかした際に、
「変数が引き継がれてると思ったのに、まったく引き継がれてないでござる！」とかなってましたが、
前回の記事の部分を調べている間にすごく単純なルールなんだと気づきました。

# 動作確認の環境[^1]
* Windows10のHyper-V上で動いているCentOS7で動作を確認
* Ubuntuなどのディストリビューションでは動作が異なるかもだけど、今回は言及しない

# 結論
頭に留めるべき原理原則は非常に明快なので、先に結論を書きます。

* **シェル変数やaliasなどの設定はすべて引き継がれない**
* **環境変数はすべて引き継がれる**

（「シェル変数」や「環境変数」の説明については、お手数ですが各自でおググりください）
多分特に意識せずとも、こういうもんだと理解している人も多いかと思います。
結論さえわかってしまえば、別にそれほど注意深く考えなくても良いかと思いますので
以降では、ログイン時の`.bash_profile`の挙動を確認して、
あとは、一見原則に反するように見える、勘違いしやすい挙動をいくつか検証していきます。

# ログインで.bash_profileを読み込んだ後の２つの対話シェル
## 挙動の確認
前回の記事で検証したように、sshで対話シェル接続した際も、GUIログイン後にターミナルを立ち上げた時も、
どちらも`.bash_profile`を読み込んだ結果が対話シェルに反映されていました。
しかし実際には、GUIログイン後のターミナルでは環境変数だけしか適用されません。
まずはそれを確認してみます。

`.bash_profile`を編集して以下のようにしてみます。

```bash:確認用の.bash_profile

if [ -f ~/.bashrc ]; then
    . ~/.bashrc
fi

PATH=$PATH:$HOME/.local/bin:$HOME/bin
export PATH

export AAA=aaa                            #環境変数
BBB=bbb                                   #シェル変数
alias print='echo'                        #エイリアスの登録
```

この時の、sshログインとGUIのターミナルでの挙動を見てみます。
まずはsshでの対話シェル接続した時

```bash:sshで対話シェル接続した時

[user-hoge@testhost ~]$ print $AAA
aaa
[user-hoge@testhost ~]$ print $BBB
bbb
```

`print`が`echo`のエイリアスとして働いており、
環境変数`AAA`もexportしていないシェル変数`BBB`も有効になっています。

次にGUIでログイン後ターミナルを立ち上げた時

```bash:GUIでログイン後ターミナルを立ち上げた時

[user-hoge@localhost ~]$ print $AAA
bash: print: コマンドが見つかりませんでした...     #エイリアスは有効でない
[user-hoge@localhost ~]$ echo $AAA
aaa                                             #環境変数AAAは見つかる
[user-hoge@localhost ~]$ echo $BBB
                                                #変数BBBが見つからず、空文字列を出力
```

`print`というコマンドは見つからないと怒られました。
また、環境変数`AAA`は見つけることができましたが、
`BBB`というシェル変数は設定されていない状態になっています。

## 何が起きているのか
もう結論を書いているので、驚くような挙動ではないと思います。
GUIでログインしたとき、確かに`.bash_profile`は読まれましたが、
その設定を取り込んだのは、このターミナルとは別のプロセスだったのです。
（ログインとターミナル起動のタイミングが別なので、別プロセスというのも違和感はないかと）
そして、`.bash_profile`を取り込んだプロセスから環境変数だけが引き継がれ、
ほかは引き継がれなかったというのがこのターミナルのプロセスです。

一方sshログインでは、`.bash_profile`をそのターミナルが明示的に呼んでいます。
`source`というコマンドは指定されたファイルを自分のプロセス内で実行するというコマンドですが、
sshログインではターミナルのプロセスが`source .bash_profile`を実行していて、
`.bash_profile`と、そこから呼ばれる`.bashrc`の設定を取り込んでいます。

前回、sshログインでは`.bash_profile`と`.bashrc`が「同列になってしまう」と書いたのは
この挙動のことを言っていたのでした。

# 紛らわしい例
設定の有効範囲という話とは直接関係ないと言えば関係ない内容ですが、
こういうのが積もり積もって「意味不明な挙動」を形成するのが世の常です。

## シェルスクリプト内のalias

```bash:紛らわしい挙動１

[user-hoge@testhost ~]$ cat print.1.sh
echo $AAA
alias print='echo'
print $AAA
[user-hoge@testhost ~]$ export AAA                # AAAを環境変数として登録
[user-hoge@testhost ~]$ bash print.1.sh
aaa
print.1.sh: 行 3: print: コマンドが見つかりません    # シェルスクリプト内でaliasを定義しても使えない？
```

シェルスクリプト内でaliasを定義して、直後に使っていますが、
`print`というコマンドは見つからないと言われます。
今回の記事内容と直接関係ないですが、こういう例を見てしまうと、
aliasは適用範囲などが原則と異なるというふうに勘違いしてしまいがちです。

```bash:紛らわしい挙動１の解決

[user-hoge@testhost ~]$ cat print.2.sh
echo $AAA
alias print='echo'
shopt -s expand_aliases
print $AAA
[user-hoge@testhost ~]$ export AAA                # AAAを環境変数として登録
[user-hoge@testhost ~]$ bash print.2.sh
aaa
aaa                                               # エイリアスが有効になっている
```

シェルスクリプト内ではエイリアスを有効にする`expand_aliases`という設定がOFFになています。
なので`shopt -s expand_aliases`で、それをONにしてあげればエイリアスを使うことができます。
（シェルスクリプト内でエイリアスは普通は使わないと思いますが）
上記を踏まえれば、この挙動は設定の引継ぎとは何も関係がないことがわかりました。
（`shopt`という設定も、別のプロセスには引き継がれない設定の一つ、という意味で関係はありますが…）

## sshでコマンド実行時のalias


```bash:紛らわしい挙動２（前半）

[user-hoge@testhost ~]$ cat ~/.bashrc
echo "read .bashrc"
export AAA=aaa
BBB=bbb
alias print='echo'
[user-hoge@testhost ~]$ ssh localhost 'print $AAA'
read .bashrc
bash: print: command not found                      # printは見つからない
[user-hoge@testhost ~]$ ssh localhost 'shopt -s expand_aliases; print $AAA'
read .bashrc
bash: print: command not found                      # printは見つからない
```

ここまで見てきた原則で、ぜひ上記の挙動を考えて見てください。

答え。
まずsshでコマンドを実行する際には、`.bashrc`が呼ばれます[^1]から、`read .bashrc`と表示されます。
次にエイリアスですが、単純に`print`を読んだだけではダメでした。
上で見たのと同様、sshの中でもエイリアスは無効になっている可能性が高そうですので、
`shopt -s expand_aliases`をつけてみましたが、やはりダメでした。
ということで、sshでコマンド実行した際、`.bashrc`を読み込んだプロセスは
コマンドを実行するプロセスとは別のプロセスであると予測できます。

さて、これには続きがありまして、

```bash:紛らわしい挙動２（後半）
[user-hoge@testhost ~]$ ssh localhost 'echo $AAA'
read .bashrc
aaa                                                 # AAAは見つかる
[user-hoge@testhost ~]$ ssh localhost 'echo $BBB'
read .bashrc
bbb                                                 # BBBも見つかる！？
```

ん！？　シェル変数であるはずのBBBまで読めてしまいました。
シェル変数が読めたので、このプロセスは、自分で`source .bashrc`をしているはずです。

さっきと矛盾してしまいました。
もしや、シェル変数とエイリアスは、やはり何か引き継ぎのルールが違うのでしょうか？
いやいや、先に結論を書いてますし、いまさら原理原則を反故にはしないのでご安心を。

```bash:紛らわしい挙動２の解決
[user-hoge@testhost ~]$ ssh localhost 'shopt -s expand_aliases; eval "print $AAA"'
read .bashrc
aaa
[user-hoge@testhost ~]$ ssh localhost 'shopt -s expand_aliases; eval "print $BBB"'
read .bashrc
bbb
```

このように、`eval`で評価してやれば、`print`のエイリアスがきちんと適用されていることがわかります。
なお、`eval`の用法を私はそこまでよく知らないのですが、エイリアスがちゃんと適用されていて、
`.bashrc`はコマンド実行と同じプロセスで実行されていることが確認できたので私は満足です。

実は記事を書き始めてからこれにあたったので、その時は焦りました・・・。
しかし、無理矢理でも原理原則が正しいってことにしておきたいという意志のおかげで
上記のやり方に気づくことができました。あぶなかったー！

ということで、 **原理原則は正しかった！**

## 単純に適用範囲を調べるなら
```bash:aliasとshoptの表示

[user-hoge@localhost ~]$ alias
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
alias grep='grep --color=auto'
alias l.='ls -d .* --color=auto'
alias ll='ls -l --color=auto'
alias ls='ls --color=auto'
alias vi='vim'
[user-hoge@localhost ~]$ shopt
autocd          off
cdable_vars     off
cdspell         off
expand_aliases  on
（一部抜粋）
```

設定が適用されているか調べるだけなら、上記のようにすれば良い。
ただ、適用されているはずなのに動作しない、というのは気持ち悪いので、
結局動作する方法を調べざるを得なくなる・・・。

# 環境変数は誰が管理している？
なんで環境変数だけが特別なのか？
「OSがそう決めているから」という、それ以上でも以下でもない[^2]答えで十分ですが、
いくらか実感できそうな材料が調達できたので書いておきます。

例えばsshログインの状態から、`exec`を使ってシェルをtschに切り替えてみます

```bash:execでtcshにシェルを切り替える

[user-hoge@localhost ~]$ export AAA=aaa
[user-hoge@localhost ~]$ BBB=bbb
[user-hoge@localhost ~]$ exec tcsh
[user-hoge@localhost ~]$ echo $AAA
aaa
[user-hoge@localhost ~]$ echo $BBB

```

execを使ってtschに切り替えたので、シェルの設定はすべてリセットされたはずですが、
環境変数`AAA`は相変わらず設定されたままです。
つまり、環境変数は「シェル」というものが管理するものではなく、
もっと上位の（OS寄りの）部分が担当しているのでは、と当たりをつけます。

## 環境変数の格納場所
その管理場所はどこかと探してみたら、ありました。
`/proc`の中におあつらえ向きな名前のファイル[^3]がありました。
GUIログイン後立ち上げたターミナルで確認してみます。

```bash:/proc/$$/environ

[user-hoge@localhost ~]$ echo $$                 # "$$"は現在のPIDを返す
3943
[user-hoge@localhost ~]$ cat /proc/3943/environ | tr '\0' '\n'
UESR=user-hoge
LANG=ja.JP_UTF-8
AAA=aaa
（一部だけ抜粋して表示。BBBは存在しない）
```

ということで、環境変数はシェルではなく、プロセスの持ち物なのでした。
まぁ、プロセスの持ち物だから子プロセスにも引き継がれる、という道理もないので、
やっぱり、結局OSがそういう仕組みになっているというだけの話ですが、
少なくともシェル変数やエイリアスとは別の場所で管理されているというのが実感できた気がします。

## environを見るときの注意点
詳細はわかりませんが、`/proc/$$/environ`の中の情報は、
そのプロセスが生成されたときに設定されたものが見えているようです。

だから、sshログインしたシェルで、自分のenvironを見てみると`AAA` は存在しません。
sshログインした場合、`source .bash_profile`が明示的に呼ばれているため、
このプロセスが生成された時点ではまだ環境変数`AAA`はまだ設定されていないからです。

ただ、そのプロセスには確かに環境変数が設定されていますし、
そのプロセスから生まれる子プロセスにも環境変数がちゃんと引き継がれます。
（子プロセスの`environ`には、新たに加えた環境変数も表示されます）

# まとめ
以上、うだうだ能書きを垂れてしまったので、
ぜひ上に戻って結論の部分だけをもう一度見て頂ければと思います。

[^1]: 詳しくは前回の記事参照
[^2]: 「以上でも以下でもない」って言葉、「ちょうどそれ」も除外しちゃってるけど、いいんかな？
[^3]: 正確には`proc`以下に見えているものはファイルではないそうです。