pipe() {
mkfifo -m 600 /tmp/tmp.$$.pipe
: >/tmp/tmp.$$.pipe &
eval "exec $1</tmp/tmp.$$.pipe"
eval "exec $2>/tmp/tmp.$$.pipe"
rm /tmp/tmp.$$.pipe
}
pipe 3 4
echo "The Message" >&4
exec 4>&-
cat <&3 # print The Message
exec 3<&-
pipe(2) は pipe を作成する関数。 sh には残念ながら pipe コマンドがなく、 C と同じことはできない。 また、新たなファイルディスクリプタ (fd) を作成するという pipe の機能は、 外部プログラムでは実現できない。 (開かれた fd を sh に渡す方法がない) この関数は、 FIFO を pipe の代わりに利用することで、この問題を回避する。 pipe と FIFO は open するまでの挙動が違っているが、 open してからファイルを unlink してしまえば、おそらく同じように機能する。
一時的にファイルシステム上に FIFO を作成する必要があるという欠点はあるが、
この関数で機能をだいたい代替できる。
$$
の値はサブシェルの中でも変わらないため、
サブシェルの中で呼び出す場合は競合しないように注意する必要がある。
pipe(2) は空いている fd で pipe を作成してその番号を返すが、 この関数は呼び出し側で利用する fd を指定する。 sh のリダイレクトの書き方を考えれば、このやり方は悪くないだろう。
実装中の :
の行は地味に重要である。
FIFO はもう一方の端点が open されるまで open をブロックするため、
バックグラウンドで一方の端点の open を予め呼んでおく必要があった。
&
は subshell を生成するため、残念ながらここで open した
fd は利用できない。
:
の実行が終わり次第 close されることになる。
なお、 FIFO を rw で open した場合の挙動は、 POSIX では未定義である。
pipe 3 4
(echo "hello, world." 3<&- 4>&-; echo $? >&4) | tr "hw." "HW!" 3<&- 4>&-
exec 4>&-
echo status of left: $(cat <&3)
exec 3<&-
sh では、 pipeline の一番右側のコマンドの exit code が 全体の exit code になる。 しかし、 pipeline の左や中のコマンドの exit code を知りたいことは、 たまによくある。
パイプラインを構成する各コマンドは subshell で実行されるため、 親 shell にその情報を受け渡すには、 プロセス間通信を利用する他ないはずである。 そこで、 pipe (正確には、 FIFO) による通信を利用して exit code を伝達する実装をしている。 (上記 pipe() はこのために作成したものである)
pipeline 中のコマンドの 3<&- 4>&-
は、
多くの場合つけなくても害がない。
exit code 受け渡し用の fd がコマンドに渡るのはあまり気分が良くないため、
このようにした。
一方 cat <&3
の前の exec 4>&-
はかなり重要である。 cat <&3
の実行前に
FIFO のすべての書き込み用 fd を close しておかないと、
cat が停止してしまう。
ちなみに、
左側のコマンドが異常終了したときにスクリプトを終了したいだけなら、
このようにして実現できる:
(left-command || kill $$) | right-command
apack(){
while [ $# -gt 0 ]; do
printf "'"
printf "%s" "$1" | sed -e "s/'/'\\\\''/g"
shift
if [ $# -gt 0 ]; then
printf "' "
else
echo "'"
fi
done
}
alen(){
eval "set -- $1"
echo $#
}
aat(){
eval "set -- $1; printf '%s\\n' \"\${$(($2+1))}\""
}
alast(){
eval "set -- $1"
eval "printf '%s\\n' \"\${$#}\""
}
afirst(){
eval "set -- $1"
printf '%s\n' "$1"
}
apush(){
echo "$1 $(shift; apack "$@")"
}
aunshift(){
echo "$(shift; apack "$@") $1"
}
aslice(){
if [ $# -le 2 ]; then
eval "set -- $2 $1 0"
else
[ $3 -le 0 ] && return
eval "set -- $2 $1 $(($(alen "$1")-$2-$3+2))"
fi
[ $1 -ge $# ] && return
shift $(($1+1))
[ $# -ge 2 ] || return
while :; do
printf "'"
printf "%s" "$1" | sed -e "s/'/'\\\\''/g"
if [ $# -gt $(eval "echo \${$#}") -a $# -ge 3 ]; then
printf "' "
shift
else
echo "'"
break
fi
done
}
aset(){
while [ $(alen "$1") -lt $2 ]; do
set -- "$1 ''" $2 "$3"
done
echo "$(aslice "$1" 0 $2)" "$(apack "$3")" "$(aslice "$1" $(($2+1)))"
}
apop(){
if [ $# -lt 2 ]; then
aslice "$1" 0 $(($(alen "$1")-1))
else
aslice "$1" 0 $(($(alen "$1")-$2))
fi
}
ashift(){
if [ $# -lt 2 ]; then
aslice "$1" 1
else
aslice "$1" "$2"
fi
}
apack element ...
alen array
aat array index
alast array
afirst array
apush array element ...
aunshift array element ...
aslice array from [len]
aset array index element
apop array [num]
ashift array [num]
divert(){
set -- "$(
eval "set -- $1";
while [ $# -gt 0 ]; do
echo "$1='$(
eval "printf '%s' \"\$$1\"" | sed "s/'/'\\\\''/g"
echo \'
);"
shift
done
)" "$@"
eval "$(
shift 2
while [ $# -gt 0 ]; do
printf "'%s " "$(printf "%s" "$1" | sed "s/'/'\\\\''/g"; echo \')"
shift
done
); set -- \$?; $1 return \$1"
}
myfunc_inner(){
i=0
a=
while [ $# -gt 0 ]; do
a="$a$1"
printf "%4d %s\n" $((++i)) "$a"
shift
done
}
myfunc(){
divert "i a" myfunc_inner "$@"
}
第一引数にスペース区切りで "退避" させたい変数を列挙しつつ、 第二以降の引数で関数名とその引数を渡して実行する。
divert 関数は第一引数で渡された変数を退避させてから指定の関数を実行する。 その後、退避した値を復元する。 結果、指定した変数がダイナミックスコースプのローカル変数として振る舞う。 divert 関数自身は位置パラメータしか使わないため、 関数から戻るときに (多分) すべてもとに戻る。