我想在.xprofile
使用时分别记录标准输出和标准错误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 shell 没有提供此类事情的直接推论,但对其进行模拟却非常简单。您需要做的就是创建一个文件,通过命令替换将其名称打印到标准,然后在同一后台运行您的命令,该命令将输出到该文件。现在您可以重定向到该扩展的值 -确切地就像您对流程替换所做的那样。因此,POSIX shell 当然提供了您需要的所有工具 - 所需要的只是您以适合您的方式使用它们。
上述两个版本都确保它们在使用它们之前销毁它们创建/使用的管道的文件系统链接。这意味着事后不需要清理,更重要的是,它们的流仅适用于最初打开它们的进程 - 因此它们的文件系统链接不能用作窥探/劫持您的日志记录活动的手段。将它们的文件系统链接留在文件系统中是一个潜在的安全漏洞。
另一种方法是包裹它。它可以在脚本内完成。
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 等效项。您只能使用 执行重定向exec
,而不能使用 fork。管道需要叉子,外壳等待子进程完成。
一种解决方案是将所有代码放在一个函数中。
all_my_code () {
…
}
{ all_my_code |
logger --priority user.notice --tag "$(basename "$0")"; } 2>&1 |
logger --priority user.error --tag "$(basename "$0")"
(这还会将记录器的 stdout 实例中的任何错误记录到 stderr 实例。您可以通过更多的文件描述符改组来避免这种情况。)
如果您希望父 shell 在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"