Open18

screenではシェルをサブシェルとして開くとは

astkastk

man bash のINVOCATIONの項を読む

astkastk

A login shell is one whose first character of argument zero is a -, or one started with the --login option.

$0- ってどういう状態なんだろう。コマンドというとシェルで実行するイメージしかなかったけど、他の方法で実行するとそういう引数になるんだろうか。

An interactive shell is one started without non-option arguments (unless -s is specified) and without the -c option whose standard input and error are both connected to terminals (as determined by isatty(3)), or one started with the -i option. PS1 is set and $- includes i if bash is interactive, allowing a shell script or a startup file to test this state.

これはインタラクティブシェルの説明。この二つを満たすとインタラクティブログインシェルとしての実行となる。

When bash is invoked as an interactive login shell, or as a non-interactive shell with the --login option, it first reads
and executes commands from the file /etc/profile, if that file exists. After reading that file, it looks for ~/.bash_pro‐
file, ~/.bash_login, and ~/.profile, in that order, and reads and executes commands from the first one that exists and is
readable. The --noprofile option may be used when the shell is started to inhibit this behavior.

その場合のプロファイルの読み込みは /etc/profile → {~/.bash_profile, ~/.bash_login, ~/.profile のどれか1つ} となる。手元の環境だと.bashrcは.profileから読み込まれている。

When an interactive shell that is not a login shell is started, bash reads and executes commands from /etc/bash.bashrc and
~/.bashrc, if these files exist. This may be inhibited by using the --norc option. The --rcfile file option will force
bash to read and execute commands from file instead of /etc/bash.bashrc and ~/.bashrc.

ログインシェルじゃないインタラクティブシェルの場合のプロファイルの読み込みは /etc/bash.bashrc → ~/.bashrc の順に読み込まれる。サブシェルはこの場合なのかな。bashrc はログインシェルじゃない場合には直接呼ばれるというのがなかなかややこしい。

astkastk

サブシェルの記述が見つからなかったけれど「シェルのサブプロセスとして実行されるシェル」という認識で良いか。

astkastk

シェル変数でサブシェルのネストは見れるらしい。環境変数ではないので env では表示されない。

$ echo $(echo $BASH_SUBSHELL)
1
$ echo $(echo $(echo $BASH_SUBSHELL))
2

あたりのシェル変数を一覧してみる。

$ set | grep ^BASH
BASH=/usr/bin/bash
BASHOPTS=checkwinsize:cmdhist:complete_fullquote:expand_aliases:extglob:extquote:force_fignore:globasciiranges:histappend:interactive_comments:progcomp:promptvars:sourcepath
BASH_ALIASES=()
BASH_ARGC=([0]="0")
BASH_ARGV=()
BASH_CMDS=()
BASH_COMPLETION_VERSINFO=([0]="2" [1]="8")
BASH_LINENO=()
BASH_SOURCE=()
BASH_SUBSHELL=0
BASH_VERSINFO=([0]="5" [1]="0" [2]="3" [3]="1" [4]="release" [5]="arm-unknown-linux-gnueabihf")
BASH_VERSION='5.0.3(1)-release'

なるほど

$ echo $(set | grep ^BASH_SUBSHELL)
BASH_SUBSHELL=0

おや、 BASH_SUBSHELL=1 になると思ったら、なんか勘違いがある気がする。

$ echo $(echo $BASH_SUBSHELL)
1

この場合の $BASH_SUBSHELL って親シェルの値に即展開されるんだっけ。

astkastk

これ .profile をどう使うのがいいのか、というのにも関わってくる。てっきりzshでも使えるプロファイルかと思ってたけど.profileの中身ってよく見たら.bashメインな感じもあるしな。

# if running bash
if [ -n "$BASH_VERSION" ]; then
    # include .bashrc if it exists
    if [ -f "$HOME/.bashrc" ]; then
	. "$HOME/.bashrc"
    fi
fi

でもbashじゃない場合も考えられていそうなのでやっぱり別のシェルプログラム向けでもあるのかも。

astkastk

echo を仕込んでプロフィールを読み込んでみる。まずは ssh で入る。

loaded /etc/profile
loaded /etc/bash.bashrc # from 
loaded ~/.profile
loaded ~/.bashrc

/etc/bash.bashrc は /etc/bash.bashrc から、~/.bashrc は ~/.profile から読み込まれている。次にこのまま screen に入る。

loaded /etc/bash.bashrc
loaded ~/.bashrc

直接bashrc系のファイルが読まれる。

astkastk

bash --norc --noprofile みたいなオプションがあるってことはprofileとrc系のプロファイルは用途が決められているんだろうな。manを見る。

       --noprofile
              Do not read either the system-wide startup file /etc/profile or any of the personal initialization files ~/.bash_pro‐
              file,  ~/.bash_login, or ~/.profile.  By default, bash reads these files when it is invoked as a login shell (see IN‐
              VOCATION below).

       --norc Do not read and execute the system wide initialization file /etc/bash.bashrc and  the  personal  initialization  file
              ~/.bashrc if the shell is interactive.  This option is on by default if the shell is invoked as sh.

profile系はstartup fileと呼ばれていてログインシェルで読み込まれる。rc系はinitialization fileと呼ばれている。たぶんPATHみたいに既存の値に付け足すタイプの定義がサブシェルで複数回処理されるのを避けているんだろう。

なのでprofile系にはサブプロセスでも引き継がれる環境変数を書いて、aliasあたりの定義はrcに書いたほうが良さそう。

astkastk

ちょっと待って、シェル変数がよくわからなくなっている。シェル変数って親シェルの値を引き継ぐんだっけ…

astkastk

実験してみる。

pi@pi2a:~ $ myvar=Hi!
pi@pi2a:~ $ set | grep myvar
myvar='Hi!'

# in subshell
pi@pi2a:~ $ echo $(set | grep myvar)
myvar='Hi!'

# in subprocess shell
pi@pi2a:~ $ bash -c 'set' | grep myvar
# => no output

サブシェルとサブプロセスのシェルは挙動が違うのかな。

astkastk

曖昧な知識から挙動を推測すると

  • bashからのコマンドとしてbashを実行するとプロセスがforkしてexecでbashが実行される
    • シェル変数は引き継がれず、子プロセスなので環境変数は引き継がれる
  • $(…) で実行されるサブシェルではシェル変数が引き継がれる
    • サブシェルって何なんだ…
astkastk

いろんな方法でサブのシェルを実行してpstreeを見てみる。現在実行中のインタラクティブシェルのbashを親シェルとする。

まずはcompound commandで実行してみる。

(list) list is executed in a subshell environment (see COMMAND EXECUTION ENVIRONMENT below). Variable assignments and
builtin commands that affect the shell's environment do not remain in effect after the command completes. The return
status is the exit status of list.

コマンドを「サブシェル」で実行するらしい。

pi@pi2a:~ $ pstree $$ -p
bash(7996)───pstree(8209)

pi@pi2a:~ $ (sleep 9999) &
[1] 8210
pi@pi2a:~ $ pstree $$ -p
bash(7996)─┬─pstree(8211)
           └─sleep(8210)

親シェルのサブプロセスとして実行される。後で試したが sleep 9999 & でも同じだった。

次にcommand substitution $(command) で実行してみる。

Bash performs the expansion by executing command in a subshell environment and replacing the command substitution with the standard output of the command, with any trailing newlines deleted.

コマンドを「サブシェル」で実行するらしい。

pi@pi2a:~ $ echo $(sleep 9999) &
[2] 8213
pi@pi2a:~ $ pstree $$ -p
bash(7996)─┬─bash(8213)───sleep(8214)
           ├─pstree(8215)
           └─sleep(8210)

別のbashがサブプロセスとして動いて、そのサブプロセスとしてsleepが動いた。

最後に bash のコマンドとして実行してみる。

pi@pi2a:~ $ bash -c 'sleep 9999' &
[3] 8217
pi@pi2a:~ $ pstree $$ -p
bash(7996)─┬─bash(8213)───sleep(8214)
           ├─pstree(8218)
           ├─sleep(8210)
           └─sleep(8217)

sleep がそのまま生えてきた。これはちょっとよくわからない。

astkastk

bashからコマンドとして呼んだbashはmanで言われているサブシェルではないんだな多分。という現在の認識。シェル変数もその呼び出し方では引き継がれない。

astkastk

manを見ている中で見つけたこんなのあったんだメモ

astkastk

If |& is used, command's standard error, in addition to its standard output, is connected to command2's standard input through the pipe; it is shorthand for 2>&1 |.

以前見た気がしたけど |& でエラーもまとめてパイプできる。

astkastk

coproc [NAME] command [redirections]
A coprocess is a shell command preceded by the coproc reserved word. A coprocess is executed asynchronously in a subshell,
as if the command had been terminated with the & control operator, with a two-way pipe established between the executing
shell and the coprocess.

これは & とはまた別なのかな。