我试图弄清楚如何使用重定向来有选择地从 bash 函数输出字符串,该函数需要将多行打印到终端。我找到了一个答案StackOverflow 上的问题。下面的代码有效。但我不明白如何或为什么。
#!/bin/bash
exec 3>&1
returnString()
{
exec 4>&1 >&3
local s=$1
s=${s:="some default string"}
echo "writing to stdout"
echo "writing to stderr" >&2
exec >&4-
echo "$s"
}
my_string=$(returnString "$*")
echo "my_string: [$my_string]"
我知道 fd3 被重定向到stdout
开头。然后fd4也是如此,1指向3,这基本上将其指向自身。但是,这对打印到终端的内容有何影响?为什么该输出没有显示在 中$my_string
,但在摆脱 fd4 后的最后一个输出中却显示了?
我不明白 fd4 与什么有什么关系,因为没有任何内容明确发送到 fd4。
答案1
您上面展示的解决方案是一种通用解决方案,涵盖了很多案例。除非您知道自己有问题,否则您可能需要采取 YAGNI(您不需要它)姿势,并将所选行重定向到 /dev/tty。
如果您想避免这种情况,那么您需要了解正在发生的文件描述符(fd)杂耍。
将3>&1
调用上下文的标准输出复制到“保存单元”,以便该函数可以输出到调用上下文的标准输出。在本例中,它是/dev/tty。请记住,该函数将使用属于 ${} 替换子 shell 的 stdout 来调用。
创建exec 4>&1
fd1 的副本,它是函数的标准输出。 fd4 被用作函数开始时的标准输出的保持单元。正在>&3
将函数/子 shell 的标准输出设置为调用者的标准输出。因此,所有输出都将发送到调用者的标准输出。 UNTIL,最后将>&4-
保存的标准输出移回其开始位置,允许最后一个echo "$s"
位于函数(和子 shell)的标准输出中。
哇!
and>&3
也>&4-
可以更清楚地写为1>&3
and 1>&4-
。
手册中有关重定向的部分bash
解释了所有的细节和术语。我很高兴也惊讶地发现实际上可以使用 {name} 形式将数字更改为单词。
“每个可能以文件描述符编号开头的重定向可能会以 {varname} 形式的单词开头。在这种情况下,对于除 >&- 和 <&- 之外的每个重定向运算符,shell 将分配一个文件描述符大于 10 并将其分配给 varname。如果 >&- 或 <&- 前面有 {varname},则 varname 的值定义要关闭的文件描述符。”
我没有对此进行测试,但它建议您可以将其编码为:
#!/bin/bash
exec {caller_stdio}>&1
returnString() {
exec {func_stdio}>&1 1>&{caller_stdio}
local s=$1
s=${s:="some default string"}
echo "writing to stdout"
echo "writing to stderr" >&2
exec 1>&{func_stdio}-
echo "$s"
}
my_string=$(returnString "$*")
echo "my_string: [$my_string]"
答案2
exec 3>&1
fd 3 现在是重复fd 1 (在您的示例中,您的终端)。正如手册页所述,这意味着 fd 1 和 fd 3 可以互换使用来引用相同的文件或设备。
shell 中 duping 的主要用途是保存 fd 的副本,以便以后恢复。
my_string=$(returnString "$*")
为了让 bash 从 的求值中获得标准输出returnString "$*"
,它创建了一个管道和叉子,孩子叫道杜普2到移动管道的写入端(在本例中为 fd 5,但可能会有所不同)到 fd 1。
exec 4>&1 >&3
重定向是从左到右评估的。 fd 4 设置为管端的重复。然后,fd 1 被设置为引用您的终端的 fd 的重复。
echo "writing to stdout"
回显到 fd 1,您的终端。
exec >&4-
这会将 fd 4 移动(即执行 dup2)到 fd 1。fd 1 现在是管道末端。
echo "$s"
回声传至管端。最终,父 shell 从管道的另一端读取此值并用作$(returnString "$*")