POSIX シェルの PID の話

PPID がサブシェルの中でも一緒というのは…なんとかならんものか.ジョブコントロールについて結構気にしている割には,プロセス番号をうまく知る術がないというのはちょっといただけないと,思うのだが….BASH の場合は BASHPID というのを用意してくれているというのに.

なお jobs ユーティリティは,あくまでバックグラウンド実行したプロセスを表示するもので…ただ,バックグラウンド実行した以外のものを気にしないといけないかというと,それは…ないのか?フォアグラウンド実行中のものは制御せずとも(制御主体なので).

set -e の罠

プログラム作成にあたっては,ある箇所のエラーにより後続の処理が有害な挙動を起こしたりしないよう注意が必要だし,そもそもエラーが発生したらそれを確実に捕捉するようにすることが重要だ.シェルスクリプトでは set -e というエラー捕捉に便利な仕組みが利用できるが,無邪気に使うと痛い目に遭いそうだ.

#!/bin/sh

set -e

FUNC () {
    false
}

FUNC

exit 0

# bottom of file

たとえばこれは,必ず失敗する false コマンドだけが入った関数 FUNC() を単に呼び出しているが,実行すると以下の通り.

$ ./func0.sh
$ echo $?
1

この結果は素直に受け容れられる.では次はどうか?

#!/bin/sh

set -e

FUNC () {
    false
}

FUNC && printf "FUNC() succeeded.\n"

exit 0

# bottom of file

実行すると以下の通り.

$ ./func1.sh
$ echo $?
0

FUNC() が失敗して printf が実行されないのは期待通りだが,この行の実行が失敗 → 「set -e のはたらきで即 exit 1」とはなっていないことがわかる.これは,POSIX ドキュメントこのあたりを読めば,「ああそういうこと」となるのだけども,直観的にそういう挙動を想像できるかというと,そうでもない.「このあたり」を抜粋しておく.

2. The -e setting shall be ignored when executing the compound list following the whileuntilif, or elif reserved word, a pipeline beginning with the ! reserved word, or any command of an AND-OR list other than the last.

このあたり より

この他に,パイプライン中のコマンドの失敗では set -e 無効,while なんたらのあとのリストでは set -e 無効,サブシェルではない複文({} のことか)内のコマンドでは set -e 無効,が書かれている.

“$@” の謎

シェルスクリプトの引数や,スクリプト内関数の引数へのアクセスについて.

$@ が引数へのアクセスを提供する.これは Positional Parameters と呼ばれ「配列のようなもの」と考えられる.

#!/bin/sh

printf "argc=%d\n" "$#"

i=1
while [ $# -gt 0 ]
do
    printf "argv[%d]=\"%s\"\n" "$i" "$1"
    shift
    i=$((i+1))
done

# bottom of file

たとえば,上のスクリプトに適当な引数を付けて実行すると,以下のような出力となる.

$ ./args0.sh "This is a pen." "How are you doing?"
argc=2
argv[1]="This is a pen."
argv[2]="How are you doing?"

shift を用いて引数(配列)の位置をずらしながら $1 にアクセスすることで全引数にアクセスしている.

#!/bin/sh

FUNC () {

    printf "argc=%d\n" "$#"
    
    i=1
    while [ $# -gt 0 ]
    do
        printf "argv[%d]=\"%s\"\n" "$i" "$1"
        shift
        i=$((i+1))
    done
    
}

FUNC $@

# bottom of file

これを関数に渡すときは注意が必要で,$@ をクオートしない(17行目)と以下のように,スペースでバラバラに区切られた引数として関数に渡ってしまう.

$ ./func0.sh "This is a pen." "How are you doing?"
argc=8
argv[1]="This"
argv[2]="is"
argv[3]="a"
argv[4]="pen."
argv[5]="How"
argv[6]="are"
argv[7]="you"
argv[8]="doing?"

これを防ぐために,関数に渡すときは "$@" とクオートするのがベストプラクティスだ.

#!/bin/sh

FUNC () {

    printf "argc=%d\n" "$#"
    
    i=1
    while [ $# -gt 0 ]
    do
        printf "argv[%d]=\"%s\"\n" "$i" "$1"
        shift
        i=$((i+1))
    done
    
}

FUNC "$@"

# bottom of file

とすると,以下のように一般に期待される結果となる.

$ ./func1.sh "This is a pen." "How are you doing?"
argc=2
argv[1]="This is a pen."
argv[2]="How are you doing?"

でも,何も背景を理解しないままプログラムの字面から期待してしまうのは,argc=1argv[1]=""This is a pen." "How are you doing?"" あたりなのだが…,この辺のことは,POSIX の仕様2.5.2 Special Parameters あたりを解読すれば理解できるのだろうが,正直ちょっと意味不明だ.

ついでに,もう一つの Positional Parameter である $* と対比しておくと,

#!/bin/sh

FUNC () {

    printf "argc=%d\n" "$#"
    
    i=1
    while [ $# -gt 0 ]
    do
        printf "argv[%d]=\"%s\"\n" "$i" "$1"
        shift
        i=$((i+1))
    done
    
}

FUNC "$*"

# bottom of file

は,以下の様に動く.

$ ./func2.sh "This is a pen." "How are you doing?"
argc=1
argv[1]="This is a pen. How are you doing?"