シェルはいくつも起動する

シェル(Bash)はユーザとカーネルを仲介しているが、Linuxに一つだけではなく同時にいくつも起動する。しかも、下記のようにちょっとした違いで。


    対話的なシェルと非対話的なシェル

    教科書的な定義はman bashinfo bashのマニュアルにあるがちょっと分かりにくい。

    対話的なシェル

    対話的な(インタラクティブ)シェルは、端末を起動したときのように、キーボードからのコマンド入力を受け付けているシェル。つまり仮想コンソールやsshによるログインもそう。その他として、bash -i シェルスクリプトで実行したもの。

    非対話的なシェル

    デスクトップ画面にログインしたときや、シェルスクリプトを通常実行した際に起動されたシェル。他に、scpコマンドでファイル送受信した際や、sshにリモートで実行するコマンドを付加した際にリモートで起動されたシェル。

    見分け方

    対話的なシェルは、環境変数PS1が設定され、また$-に「i」が含まれている。(man bashより)

    # /bin/bashrcにある例
    if [ "$PS1" ]; then
      echo '対話的なシェル'
    fi
    if [ "${-#*i}" != "$-" ]; then
      echo '対話的なシェル'
    fi
    
    # これらをシェルスクリプトで通常実行(-iなし)すると何も出力されないが、そのまま端末に貼り付けて実行すると出力される
    

    このページでシェルスクリプトの通常実行とは、bashコマンドに-i-lオプションを付けたり、sourceコマンドを用いたりしたものではないものを指す。つまり./シェルスクリプト.shbash シェルスクリプト.shという実行方法。


    ログインシェルと非ログインシェル

    ログインシェル

    その他として、bash -l シェルスクリプトで実行したもの。

    非ログインシェル

    最初の二つは意外にも?!非ログインシェル。

    見分け方

    ログインシェルかどうかは、shoptコマンドの変数login_shellでわかる。(man builtinsより)

    # /bin/bashrcにある例
    if ! shopt -q login_shell ; then
      echo '非ログインシェル'
    fi
    # 開いたばかりのGNOME端末に貼り付けて実行すると出力される
    

    いくつでも起動され得るシェル

    上記のようにシェルは組み合わせにより4種類の違いで、しかもシェルがシェルを呼ぶというようにいくつでも起動され得る。

    それぞれのシェルは環境変数などの独自環境を持つ。シェルは他のシェルの環境変数を参照できない。シェルは自身が呼び出したシェル(サブシェル)にexportコマンドで環境変数を引き継がせることができるが、その瞬間からシェルとサブシェルはお互いに変数の参照すらできない。シェル(殻)というのは他のシェルに対しても殻と言える。

    他のシェルや特にサブシェルと区別したいときにカレントシェルと表現することがある。例えば下記1行のシェルスクリプトを通常実行するとそれはサブシェルで実行されるので、サブシェルのカレントディレクトリが/tmp/に変わっただけ。サブシェルはそのままプロセスが終了となるので、見た目は何も変化なし。

    cd /tmp/

    そこで「カレントシェルのカレントディレクトリを変更するスクリプトを実行したい」となったとき、sourceコマンド(.ドットでも)でスクリプトを実行する。

    source スクリプト.sh

    上記コマンド内容ならサブシェルは起動されない。


    サブシェルで起動されるシェルスクリプト

    まずpsコマンドによってカレントシェルのプロセスID(マーク部分)を確認しておく。

    ps
      PID TTY          TIME CMD
    13889 pts/1    00:00:00 bash
    

    ここでシェルスクリプトを通常実行すると、それはサブシェルで実行される。

    例えば、pstreeコマンド(プロセスの親子関係確認)を利用した下記のシェルスクリプトでその様子がわかる。

    #!/bin/bash
    if [ $0 = 'bash' ]; then
      echo "sourceコマンドで実行したとき"
      pstree -p "$$" # $$はカレントシェルのプロセスID
    else
      echo "通常実行したとき"
      pstree -p "$PPID" # $PPIDは親のプロセスID
    fi
    

    これをbashコマンドで通常実行すると出力は、

    通常実行したとき
    bash(13889)───bash(14527)───pstree(14528)
    

    マークしたのがサブシェル。しかし、sourceコマンドで実行するとサブシェルではなくカレントシェルで実行されるので、出力は、

    sourceコマンドで実行したとき
    bash(13889)───pstree(14576)
    

    なお、sourceコマンドでシェルスクリプトがカレントシェルで実行されるだけであって、スクリプトの個々のコードがサブシェルを生成することはある。


    Bashのスタートアップファイル

    それぞれのシェルは環境変数などの独自環境を持つが、シェルの起動時に下記のスタートアップファイルで調整される。

    CentOS7の例

    1. /etc/profile
    2. /etc/profile.d/*.sh
    3. ~/.bash_profile
    4. ~/.bashrc
    5. /etc/bashrc

    ユーザ個人設定は3と4だが、基本的には4の~/.bashrcに追記する。というのも、4なら「対話的な非ログインシェル」(=GNOME端末起動とか)でも読み込まれるから。3と4のデフォルトは/etc/skel/にある。

    1と3はログインシェルの場合のみ読み込まれる。1、3の順番で。3は4を読み込んでいる。man bashによると、Bashは「~/.bash_profile~/.bash_login~/.profile」の順番で検索して最初のものを読み込むので、3は環境により違うかもしれない。

    「シェルスクリプトの通常実行」の場合はいずれも読み込まれず、それ以外のシェルはすべて2、4、5を読み込む(scpファイル送受信もリモート側シェルで)。

    2の読み込み元は、ログインシェルの場合は1、非ログインシェルの場合は5。

    以上の話で「読み込まれるか否か」というのは、決して「カレントシェルでその設定が有効か否か」という意味ではない。例えば~/.bash_profileで変数をexportしていたとすると、デスクトップにGUIでログインしたタイミングでそれが読み込まれるが、つづいて端末を開くとその瞬間には読み込まれないがexportで引き継がれた変数はカレントシェルの端末でも有効。(よって、~/.bash_profileを書き換えたら、端末再起動だけしても変更は有効にならない)