この記事の内容
前回書いた「 .bash_profileと.bashrcなんて使い分けなくてよかったんや!」 という記事では説明しなかった、
.bash_profile
や.bashrc
に書いた設定が適用される範囲などについて書いています。
もともと前回のと合わせて一つの記事にしようと考えていたのですが、
内容として独立させられると思ったのと、量が多すぎるかと思ったので分けました。
姉妹記事的な立ち位置なので、両方読んで頂けると理解が深まるかと思います。
(「別の記事を読まないと成り立たない記事」というのは書きたくないのですが、
もともと1つの記事にしようと思っていたので、かなり依存してしまっています。。。)
調べる前は、bash hoge.sh
とかした際に、
「変数が引き継がれてると思ったのに、まったく引き継がれてないでござる!」とかなってましたが、
前回の記事の部分を調べている間にすごく単純なルールなんだと気づきました。
動作確認の環境1
- Windows10のHyper-V上で動いているCentOS7で動作を確認
- Ubuntuなどのディストリビューションでは動作が異なるかもだけど、今回は言及しない
結論
頭に留めるべき原理原則は非常に明快なので、先に結論を書きます。
- シェル変数やaliasなどの設定はすべて引き継がれない
- 環境変数はすべて引き継がれる
(「シェル変数」や「環境変数」の説明については、お手数ですが各自でおググりください)
多分特に意識せずとも、こういうもんだと理解している人も多いかと思います。
結論さえわかってしまえば、別にそれほど注意深く考えなくても良いかと思いますので
以降では、ログイン時の.bash_profile
の挙動を確認して、
あとは、一見原則に反するように見える、勘違いしやすい挙動をいくつか検証していきます。
ログインで.bash_profileを読み込んだ後の2つの対話シェル
挙動の確認
前回の記事で検証したように、sshで対話シェル接続した際も、GUIログイン後にターミナルを立ち上げた時も、
どちらも.bash_profile
を読み込んだ結果が対話シェルに反映されていました。
しかし実際には、GUIログイン後のターミナルでは環境変数だけしか適用されません。
まずはそれを確認してみます。
.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での対話シェル接続した時
[user-hoge@testhost ~]$ print $AAA
aaa
[user-hoge@testhost ~]$ print $BBB
bbb
print
がecho
のエイリアスとして働いており、
環境変数AAA
もexportしていないシェル変数BBB
も有効になっています。
次に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
[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は適用範囲などが原則と異なるというふうに勘違いしてしまいがちです。
[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
[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
を読み込んだプロセスは
コマンドを実行するプロセスとは別のプロセスであると予測できます。
さて、これには続きがありまして、
[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
をしているはずです。
さっきと矛盾してしまいました。
もしや、シェル変数とエイリアスは、やはり何か引き継ぎのルールが違うのでしょうか?
いやいや、先に結論を書いてますし、いまさら原理原則を反故にはしないのでご安心を。
[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
はコマンド実行と同じプロセスで実行されていることが確認できたので私は満足です。
実は記事を書き始めてからこれにあたったので、その時は焦りました・・・。
しかし、無理矢理でも原理原則が正しいってことにしておきたいという意志のおかげで
上記のやり方に気づくことができました。あぶなかったー!
ということで、 原理原則は正しかった!
単純に適用範囲を調べるなら
[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に切り替えてみます
[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ログイン後立ち上げたターミナルで確認してみます。
[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
には、新たに加えた環境変数も表示されます)
まとめ
以上、うだうだ能書きを垂れてしまったので、
ぜひ上に戻って結論の部分だけをもう一度見て頂ければと思います。