要注意 grep

コマンドライン上で気軽に使うには問題ないが,シェルスクリプト内で使用する時は要注意.(考えてみれば,シェルスクリプト内で grep を使用することがほとんどなくなってきた…)

典型的な問題

$ cat DATA
LINE1
LINE2
LINE3

DATA というファイルの内容に LINE2 という内容が含まれていたらある「処理」を行うという動作をするプログラムは,以下のように書くことはできる.

grep -q LINE2 DATA && <処理>

が,DATA が以下のような内容になっても,「処理」が走ってしまう.(これはほとんどの場合,そうなっては欲しくないだろう)

$ cat DATA
LINE1
LINE8
LINE16
LINE24
LINE32

データによってプログラムの挙動が意図しないものになってしまう典型となるので,grep は便利だがここでは使うべきではない.

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?"

Highlighting Code Block

投稿内にプログラムコードの表示を行いたいので,Highlighting Code Block プラグインをインストールしてみた.

どのように使うか?「/」では Highlighting Code Block は選べない.画面右上の「+」を選ぶと,Highlighting Code Block が出てきて,それを選ぶと「Your Code…」と入ったブロックが表示された.カーソルを持っていくと,「- lang select -」でプログラム言語の選択肢が表示される – Bash もある!「ファイル名」を入力する欄もある.「data-line 属性値」とは?なるほど.行番号を指定するとそこがハイライトされるのか…_ん,効かないが??

#!/bin/bash

string="Hello World!"

printf "%s¥n" "$string"

exit 0

# bottom of file

と思ったら,プレビューしてみるとちゃんと効いている.行番号の表示も OK だ.