module
的功能环境模块1包通过修改各种环境变量来完成其工作当前的壳进程。
不幸的是,无论成功与否,该函数都会返回 0 2 ,这使得客户端脚本很难对失败做出适当的响应。
我想实现一个函数包装器mymodule
,module
将其所有参数直接传递给module
,并在失败时正确返回非零值module
。
mymodule
检测是否失败的唯一方法module
是检查输出module
函数写入 stderr(如果有)。
问题是我无法想出一种合理的方法来mymodule
获得此输出而不取消 的module
操作。更具体地说,我能想到的将module
stderr捕获到变量中的几乎所有方法都需要module
在子进程中运行,从而阻止它完成其工作(这需要修改当前 shell)。
上述的一个例外是将module
stderr 重定向到临时文件,但我讨厌每次函数module
运行时都创建一个文件的想法。
是否有某种方法可以在当前环境中mymodule
调用module
并同时在变量中捕获其 stderr?
zsh
我对和 的答案都很感兴趣bash
。
1不要与Lmod 环境模块包,它具有非常相似的界面。
2至少我必须使用的旧版本 3.2.9 是这样。我对此无法控制。
答案1
Bash 和 zsh 都有协进程(不幸的是略有不同),这基本上结束了一个pipe
电话并生成一个子进程,其标准输入和标准输出可供调用 shell 使用。本质上,它们让 shell在x
和y
中运行x | cmd | y
,但所有x
, y
, 和cmd
从当前进程运行,并且不是在管道执行环境中。
这将让我们从当前 shell运行module 2>&...
一些。如果我们使用作为我们的协进程(即),它只会再次直接重复所有内容,然后我们可以稍后再次将输出读回当前 shell 。...
module
cat
cmd
y <&...
另一种选择是将标准错误重定向到另一个后台进程并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 的方法不同)执行此操作将导致死锁。