Bash使いたくないけどPIPESTATUSどうしよう……
そんなアナタに耳よりな話。POSIXの範囲、つまりどのUNIXシェルでも同等のことができるというお話です。
元ネタはコチラですが、ここで例示されているコードでは、繋ぎたいコマンドが増えるとやがて動かなくなるので修正版を公開します。
1)次のシェル関数を定義
まずは、PIPESTATUSのように、全ての戻り値を集めたいコマンドパイプ列を書こうとしているシェルスクリプトの冒頭に次のシェル関数を追記します。
run() {
local a j k l com # ←ここはPOSIX範囲外なんだけど……
j=1
while eval "\${pipestatus_$j+:} false"; do
unset pipestatus_$j
j=$(($j+1))
done
j=1 com= k=1 l=
for a; do
if [ "x$a" = 'x|' ]; then
com="$com { $l "'3>&-
echo "pipestatus_'$j'=$?" >&3
} 4>&- |'
j=$(($j+1)) l=
else
l="$l \"\${$k}\"" # ←修正箇所はここ
fi
k=$(($k+1))
done
com="$com $l"' 3>&- >&4 4>&-
echo "pipestatus_'$j'=$?"'
exec 4>&1
eval "$(exec 3>&1; eval "$com")"
exec 4>&-
j=1
while eval "\${pipestatus_$j+:} false"; do
eval "[ \$pipestatus_$j -eq 0 ]" || return 1
j=$(($j+1))
done
return 0
}
オリジナルからの修正点
公開されていたソースコードから修正した箇所にコメントを入れておきました。
肝心なのは2つ目のコメントをつけた箇所です。ブレース“{}
”を追加しないと10番目($10
)以降の引数を取得できないという問題です。
1つ目のコメントは、関数内でのみ使っている変数をローカル変数として閉じ込め、外部で同名の変数を使っていても衝突しないようにするためのものです。これはPOSIXでは定義されていないのですが、今時の大抵のシェルには実装されているので付けておきました。厳密にPOSIXへ準拠させる場合は外してください。
2)使ってみる
試しに使ってみましょう。
上記のシェル関数を冒頭に書いた次のシェルスクリプトを用意します。
#! /bin/sh
# 上記のシェル関数を記述
run() {
:
}
# PIPESTATUSと同様に、実行しながら全コマンドの戻り値を集める
run \
printf 1 \| \
awk '{print $1+1}END{exit 2}' \| \
cat \| \
awk '{print $1+1}END{exit 4}' \| \
cat
# 戻り値が集められたかどうか確認する
set | grep ^pipestatus
まず、このシェル関数の使い方ですが。注意すべきことは3つ。
- パイプで繋ぐ一連のコマンド列の先頭にキーワード“
run
”をつける。 - コマンドを繋ぐパイプ記号“
|
”はエスケープする。 - 可読性のために改行を入れたい場合は、行末にバックスラッシュ“
\
”を付けることによって行う。
これは、実行させたいコマンド文字列をシェルに直接解釈させるのではなく、一旦シェル関数に渡して、実際のコマンド文字列を動的に生成させる仕組みになっているからです。
さてそれでは実行してみましょう。上記コードのシナリオですが、途中に2個のAWKがあり、それらを通るたびに、数字が+1されます。初期値はprintf
により1にしていますので、最後は3が出てくるはずです。ただし、これらのAWKは0でない戻り値を返すようにしてあります。一連の戻り値は“pipestatus_n”(nはコマンドの出現順に基づく番号)に各々格納されますが、本当にそうなっているかset
コマンドで最後に確かめる、というわけです。
$ chmod +x pipestatus_test.sh # 実行フラグを付けて……
$ ./pipestatus_test.sh # いざ実行!
3
pipestatus_1=0
pipestatus_2=2
pipestatus_3=0
pipestatus_4=4
pipestatus_5=0
$
うまくいきましたか?