前の記事ではMaybeっぽいパイプを作成したので今度はEitherっぽいパイプを作ってみる。
シェルスクリプトを書いているとこんなコードを書いてなんだかなぁという気持ちになることがある。(シェル力の問題なのかもしれないが)
x=`command` 2>$TMPDIR/cmd_err || {
my_logger "ERROR: command failed, ecode=$?, msg=`cat $TMPDIR/cmd_err`"
}
こんな感じでどうでもいい一時ファイルや変数は処理から見えないようにしたい。
# ケース1
x=`either command | fmap command | cmd 2>&1` || {
my_logger "ERROR: hoge failed, ecode=$?, msg=$x"
}
# ケース2
either command | fmap command | either_case
in 0) cat -
;; *) my_logger 'ERROR: hoge failed, ecode=%d, msg=%s' $? $(cat)
either_esac
実装例
前回のコードを素直に拡張する。
標準入出力のフォーマット
# 成功の場合
either
0
<いつもの標準出力>
# 失敗の場合
either
<0以外の数値>
<標準エラー出力>
maybeの場合はエラーの場合は出力はなかったが、eitherの場合は(ややこしいが)エラー出力が標準出力に出力する。
関数
fmapやcmd関数は前回を参照
either() {
local out=`mktemp` err=`mktemp`
{ rm $out $err
"$@" >/dev/fd/3 2>/dev/fd/4 &&
right cat /dev/fd/3 ||
left $? cat /dev/fd/4
} 3>$out 4>$err
}
right() {
echo either
echo 0
"$@"
return 0
}
left() {
local ret=$1
shift
echo either
echo $ret
"$@"
return $ret
}
fmap_either() {
bind_either either "$@"
}
bind_either() {
local value
read value
case "$value"
in 0) "$@"
;; *) left "$value" cat -
esac
}
cmd_either() {
local r
read r || exit
case $r
in 0) cat -
;; *) cat - >&2
return $r
esac
}
_eithercase() {
local r
read && read r || exit
return $r
}
alias either_case='{ _eithercase; case $?'
alias either_esac='esac; }'
使ってみる
$ x=`either a | fmap b | cmd 2>&1` || {
> echo "ERROR: command failed, ecode=$?, msg=$x"
> }
ERROR: command failed, ecode=127, msg=a: コマンドが見つかりません
これだと x=$(cmd 2>&1)
と何が違うのかと言われそうだが、正常系の場合にxに変なワーニングが入り込まないことが保証されていることが異なる。
$ either echo a | fmap a | either_case
> in 0) cat -
> ;; *) echo "ERROR: command failed, ecode=$?, msg=$(cat)"
> either_esac
ERROR: command failed, ecode=127, msg=a: コマンドが見つかりません
$
$ either echo a | fmap grep a | either_case
> in 0) cat -
> ;; *) echo "ERROR: command failed, ecode=$?, msg=$(cat)"
> either_esac
a