如何在 bash 脚本中保存 /dev/stdout 目标位置?

如何在 bash 脚本中保存 /dev/stdout 目标位置?

我有一个 bash 脚本,它希望/dev/stdout在用其他位置替换第一个文件描述符之前保留原始位置。

所以,很自然地,我写了类似的东西

old_stdout=$(readlink -f /dev/stdout)

但它不起作用。我很快就明白了问题所在:

test@ubuntu:~$ echo $(readlink -f /dev/stdout)
/proc/5175/fd/pipe:[31764]
test@ubuntu:~$ readlink -f /dev/stdout
/dev/pts/18

显然,$()在子 shell 中运行,该子 shell 通过管道传输到父 shell。

所以问题是:是否有一种可靠的(仅限于 Linux 发行版之间的可移植性)方法将/dev/stdout位置保存为 bash 脚本中的字符串?

答案1

要保存文件描述符,请将其复制到另一个文件描述符上。保存相应文件的路径是不够的,您需要保存打开模式、打开标志、文件中的当前位置等。当然,对于匿名管道或套接字,这不起作用,因为它们没有路径。你想要保存的是打开文件描述fd 所引用的,复制一个 fd 实际上是将一个新的 fd 返回给同一个 fd打开文件描述

要将文件描述符复制到另一个文件描述符上,使用类似 Bourne 的 shell,语法为:

exec 3>&1

上面,fd 1 被复制到 fd 3 上。

无论 fd 3 之前已经打开什么,都将被关闭,但请注意,fd 3 到 9(通常更多,最多 99 个yash)是为此目的而保留的(并且没有与 0、1 或 2 相反的特殊含义), shell 知道不要将它们用于自己的内部业务。 fd 3 提前打开的唯一原因是您在脚本1中执行了它,或者它被调用者泄露了。

然后,您可以将 stdout 更改为其他内容:

exec > /dev/null

然后,恢复标准输出:

exec >&3 3>&-

3>&-关闭我们不再需要的文件描述符)。

现在的问题是,除了 ksh 之外,您之后运行的每个命令exec 3>&1都将继承 fd 3。这就是 fd 泄漏。一般来说没什么大不了的,但这可能会导致问题。

ksh设置执行时关闭这些 fd 上有标志(对于超过 2 个的 fd),但其他 shell 上没有标志,并且其他 shell 没有任何方法可以手动设置该标志。

其他 shell 的解决方法是关闭每个命令的 fd 3,例如:

exec 3>&-

exec > file.log

ls 3>&-
uname 3>&-

exec >&3 3>&-

麻烦。在这里,最好的方法是exec根本不使用,而是重定向命令组:

{
  ls
  uname
} > file.log

在那里,shell 负责保存 stdout 并在之后恢复它(它确实通过将其复制到 fd 上(高于 9,高于 99 for yash)在内部执行此操作执行时关闭标志设置)。

1

现在,如果您广泛或在函数中使用这些 fds 3 到 9,那么它们的管理可能会很麻烦且有问题,特别是如果您的脚本使用一些可能反过来使用这些 fds 的第三方代码。

一些 shell ( zsh, bash, ksh93, 都添加了该功能 (奥利弗·基德尔 (Oliver Kiddle) 建议zsh)大约在 2005 年的同一时间,在开发人员讨论之后)有一种替代语法来分配第一个大于 10 的空闲 fd,这在这种情况下会有所帮助:

myfunction() {
  local fd
  exec {fd}>&1
  # stdout was duplicated onto a new fd above 10, whose actual value
  # is stored in the fd variable
  ...
  # it should even be safe to re-enter the function here
  ...
  exec >&"$fd" {fd}>&-
}

答案2

$$如果是交互式 shell 或编写相关 shell PID,则会获取当前进程 PID。

所以你可以使用:

readlink -f /proc/$$/fd/1

例子:

% readlink -f /proc/$$/fd/1
/dev/pts/33

% var=$(readlink -f /proc/$$/fd/1)

% echo $var                       
/dev/pts/33

答案3

正如您所看到的,bash 脚本不像常规编程语言那样可以分配文件描述符。

最简单的解决方案是使用子 shell 来运行您想要重定向的内容,以便可以将处理恢复到具有完整标准 I/O 的顶级 shell。

另一种解决方案是用于tty识别 TTY 设备并控制脚本中的 I/O。例如:

dev=$(tty)

然后你就可以..

echo message > $dev

相关内容