
.xprofile
.NETを使用するときは、stdoutとstderrを別々に記録したいと思いますlogger
。 Bashでは、次のように見えると思います。
exec 1> >(logger --priority user.notice --tag $(basename $0)) \
2> >(logger --priority user.error --tag $(basename $0))
POSIXでこれをどのように実行しますか? /bin/sh
互換性のある方法は?
答え1
POSIX コマンド/プロセスの置換
_log()( x=0
while [ -e "${TMPDIR:=/tmp}/$$.$((x+=1))" ]
do continue; done &&
mkfifo -- "$TMPDIR/$$.$x" &&
printf %s\\n "$TMPDIR/$$.$x" || exit
exec >&- >/dev/null
{ rm -- "$TMPDIR/$$.$x"
logger --priority user."$1" --tag "${0##*/}"
} <"$TMPDIR/$$.$x" &
) <&- </dev/null
次のように使用できる必要があります。
exec >"$(_log notice)" 2>"$(_log error)"
次のコマンドを使用するバージョンは次のとおりですmktemp
。
_log()( p=
mkfifo "${p:=$(mktemp -u)}" &&
printf %s "$p" &&
exec <&- >&- <>/dev/null >&0 &&
{ rm "$p"
logger --priority user."$1" --tag "${0##*/}"
} <"$p" &
)
mktemp
...ファイル名を選択できることを除いて、ほぼ同じことを行います。これはうまくいきます。プロセスの交換決して魔法ではなく、次のように動作します。コマンドの置き換え。拡張子をその中で実行されるコマンドの値に置き換える代わりにコマンドの置き換えする、プロセスの交換これを出力を見つけることができるファイルシステムリンクの名前に置き換えます。
POSIXシェルはこれに直接結果を与えませんが、これをシミュレートするのは非常に簡単です。あなたがしなければならないのは、ファイルを作成し、コマンドの置き換えによってその名前を標準で印刷してから、ファイルに出力されるのと同じ背景でコマンドを実行することだけです。これで、その拡張の値にリダイレクトできます。正確にプロセス交換で行ったのと同じです。したがって、POSIXシェルは必要なすべてのツールを確実に提供します。必要なのは、自分に合った方法でツールを使用することだけです。
上記の両方のバージョンを使用する前に、作成/使用するパイプのファイルシステムリンクを破壊します。つまり、後でクリーンアップする必要はなく、さらに重要なことは、そのストリームを元々開いたプロセスでのみ使用できることです。したがって、そのファイルシステムリンクをロギングアクティビティをスヌーピング/ハイジャックする手段として使用することはできません。ファイルシステムリンクをファイルシステムに残すと、潜在的なセキュリティの脆弱性が発生します。
もう一つの方法は包装することです。スクリプト内で実行できます。
x=${x##*[!0-9]*}
_log(){
logger --priority user."$1" --tag "${0##*/}"
} 2>/dev/null >&2
cd ../"$PPID.$x" 2>/dev/null &&
trap 'rm -rf -- "${TMPDIR:-/tmp}/$PPID.$x"' 0 ||
{ until cd -- "${TMPDIR:=/tmp}/$$.$x"
do mkdir -- "$TMPDIR/$$.$((x+=1))"
done &&
x=$x "$0" "$@" | _log notice
exit
} 2>&1 | _log error
これにより、スクリプトはデフォルトで自分自身を呼び出すことができ(まだ呼び出されていない場合)、起動する一時作業ディレクトリを提供できます。
答え2
POSIXに該当するものはありません。 ForkではなくPerform Redirectionのみを使用できますexec
。パイプにはフォークが必要で、シェルは子プロセスが完了するのを待ちます。
1つの解決策は、すべてのコードを関数に入れることです。
all_my_code () {
…
}
{ all_my_code |
logger --priority user.notice --tag "$(basename "$0")"; } 2>&1 |
logger --priority user.error --tag "$(basename "$0")"
(これはまた、ロガーのstdoutインスタンス内のすべてのエラーをstderrインスタンスに記録します。より多くのファイル記述子を混在させることでこれを防ぐことができます。)
logger
プロセスの実行中に親シェルを終了するには、それを呼び出し&
の終わりに入れますlogger
。
{ all_my_code |
logger --priority user.notice --tag "$(basename "$0")" & } 2>&1 |
logger --priority user.error --tag "$(basename "$0")" &
あるいは、名前付きパイプを使用することもできます。
pipe_dir=$(mktemp -d)
mkfifo "$pipe_dir/out" "$pipe_dir/err"
<"$pipe_dir/out" logger --priority user.notice --tag "$(basename "$0")" &
<"$pipe_dir/err" logger --priority user.error --tag "$(basename "$0")" &
exec >"$pipe_dir/out" 2>"$pipe_dir/err"
…
rm -r "$pipe_dir"