如何捕获修改当前环境的函数的stderr?

如何捕获修改当前环境的函数的stderr?

module的功能环境模块1包通过修改各种环境变量来完成其工作当前的壳进程。

不幸的是,无论成功与否,该函数都会返回 0 2 这使得客户端脚本很难对失败做出适当的响应。

我想实现一个函数包装器mymodulemodule将其所有参数直接传递给module,并在失败时正确返回非零值module

mymodule检测是否失败的唯一方法module是检查输出module函数写入 stderr(如果有)。

问题是我无法想出一种合理的方法来mymodule获得此输出而不取消 的module操作。更具体地说,我能想到的将modulestderr捕获到变量中的几乎所有方法都需要module在子进程中运行,从而阻止它完成其工作(这需要修改当前 shell)。

上述的一个例外是将modulestderr 重定向到临时文件,但我讨厌每次函数module运行时都创建一个文件的想法。

是否有某种方法可以在当前环境中mymodule调用module并同时在变量中捕获其 stderr?

zsh我对和 的答案都很感兴趣bash


1不要与Lmod 环境模块包,它具有非常相似的界面。

2至少我必须使用的旧版本 3.2.9 是这样。我对此无法控制。

答案1

Bash 和 zsh 都有协进程(不幸的是略有不同),这基本上结束了一个pipe电话并生成一个子进程,其标准输入和标准输出可供调用 shell 使用。本质上,它们让 shell在xy中运行x | cmd | y,但所有x, y, 和cmd从当前进程运行,并且不是在管道执行环境中。

这将让我们从当前 shell运行module 2>&...一些。如果我们使用作为我们的协进程(即),它只会再次直接重复所有内容,然后我们可以稍后再次将输出读回当前 shell 。...modulecatcmdy <&...

另一种选择是将标准错误重定向到另一个后台进程并wait获取其返回代码。我将在下面讨论这两个问题。


我将使用这个假module函数进行测试,以便我可以随意打开和关闭错误。 if 修改当前的 shell 环境,以便我们可以看到它并向 stderr 输出“err”;我将根据需要对该行进行注释:

module() {
        sleep 1
        FOO=$(date)
        echo err >&2
} 

如果我cat在 Bash 中运行协进程,我可以将module's stderr 重定向到其中,并从cat's stdout 读取以执行我想要的任何操作:

coproc cat
module 2>&${COPROC[1]}
exec {COPROC[1]}>&-
if grep -q err <&${COPROC[0]}
then
        echo got an error
else
        echo no error
fi

在 zsh 中它需要是

module 2>&p
exec 4<&p
coproc :
if grep -q err <&4

相反,在中间。

无论哪种情况,我都可以module在当前 shell 中运行命令并从那里读取错误输出。函数可以if正常返回。

一切除了cat除了从当前执行环境运行不是像管道一样创建独立的环境。我们可以echo $FOO在最后检查这一点,并看到日期已更新,因为module在当前环境中运行。


或者,后台进程可以完成所有工作。这在 Bash 中有效:

exec 2> >( if grep -q . ; then exit 7 ; else exit 0 ; fi )
PID=$!
module
exec 2>&-
wait $PID
echo $?

上面的内容将根据顶部子进程的内容输出7或 ,您可以调整以对返回代码执行任何您喜欢的操作。0在 zsh 下则不然,因为$!没有设置进程替换;这应该是可以解决的,但我停止了尝试。固定的 fifo,而不是临时文件,也可以在这里工作。

在这种情况下,您可能还想在任一侧保存和恢复 FD 2。

答案2

我不知道“环境模块”是如何工作的,但我从你的描述中假设它们是设置环境变量的 shell 函数,并且它们的 stderr 输出应该被捕获/匹配,而不需要在单独的进程中运行它们。

不管你喜欢与否,答案是唯一可靠且明显的方法是将它们的 stderr 重定向到临时文件。使用命名管道同样不方便(您仍然需要创建一个临时文件!),而且更加棘手。使用协进程是繁重、笨拙且难以携带

bash(和在bash仅)您可以利用未记录的功能$!从进程替换中设置为 PID >(...))并摆脱类似的情况:

module 2> >(grep error)
wait $! && echo failed

这个例子假设module它本身没有产生任何可能混淆的孩子$!

答案3

在 Linux 上,使用 bash 和 zsh,您应该能够执行以下操作:

my_module() {
  chmod u+w /dev/fd/3 # only needed in bash 5+
  module 2> /dev/fd/3 3>&-
  ! grep -q err /dev/fd/3
} 3<<< ''

3<<< ''是一个最初包含空行的此处字符串。zsh和都将bash此处字符串和此处文档实现为已删除的临时文件。在 Linux(和 Cygwin,但通常不是其他系统)上, opening/dev/fd/3会打开 fd 3 指向的文件,即使它已被删除(在其他系统上,它会复制 fd 3),因此这是一种处理临时文件的非常干净的方式那里。该文件已被删除,您不必担心其清理,并且它仅在 FS 上可见很短的时间(但是,从版本 5 开始,bash删除了对其的写入权限,我们需要解决此问题chmod)。

在这里,如果您要按顺序运行(而不是在单独的进程中并行运行),则确实需要一个临时module文件grep。如果有足够的数据输出填满管道,则使用管道(类似于 Michael 的方法,但与 @mosvy 的方法不同)执行此操作将导致死锁。

相关内容