如何在 Mac 的 bash 中倒回文件描述符而不使用第二个文件描述符从开头读取?

如何在 Mac 的 bash 中倒回文件描述符而不使用第二个文件描述符从开头读取?

环境:GNU bash,版本 3.2.57(1)-release (x86_64-apple-darwin20)

尝试0:

exec 3<> "$(mktemp)"    # open file descriptor 3 to a temp file for read/write
echo 'foo' >&3          # write 'foo' to descriptor 3
read bar <&3            # read contents of descriptor 3 to variable named bar (not using -u 3)
echo $?                 # exit status returns "1"
exec 3>&-               # close file descriptor 3

在上面的代码片段中,我期望$bar设置为,"foo"但它不起作用,因为文件描述符 3 中的位置位于文件描述符文件的末尾。

尝试1:

exec 3<> "$(mktemp)"    # open file descriptor 3 to a temp file for read/write
echo 'foo' >&3          # write 'foo' to descriptor 3
cat <&3                 # echo contents of of descriptor 3 to STDOUT
echo $?                 # exit status returns "0"
exec 3>&-               # close file descriptor 3

这也会失败,因为描述符 3 中的文件位置位于末尾。

在 C/C++ 中你可以倒回(...) 和 fseek(...)将创建的文件临时文件的文件指针重置mktemp到文件的开头。

已知的解决方法来自这里(使用第二个描述符)

myTempFile="$(mktemp)"  # create temp file and assign to $myTempFile
exec 3> "$myTempFile"   # open file descriptor 3 for writing to $myTempFile
exec 4< "$myTempFile"   # open file descriptor 4 for reading of $myTempFile
echo 'foo' >&3          # write 'foo' to descriptor 3
read bar <&4            # read contents of descriptor 4 to variable named bar
echo $?                 # exit status returns "0"
echo "$bar"             # echo "foo"
exec 3>&-               # close file descriptor 3
exec 4>&-               # close file descriptor 4

问题™(最后):

如何在 bash 中倒回文件描述符而不使用第二个文件描述符从头开始读取?

注意:我不想安装克什93使用其倒带功能。

exec 3<> "$(mktemp)"
echo 'foo' >&3
# Magic Happens here
read bar <&3             # expect $bar == "foo"
echo $?                  # expect "0"
exec 3>&- 

答案1

虽然这是一个有趣的问题(但也是我 30 年来从未需要用 shell 做的事情)。您可能尝试使用错误的工具。

Unix(UNIX、BSD、MacOS、Gnu/Linux)有管道。管道是一个特殊的文件(不在辅助存储/磁盘中),它有两个文件描述符。一种用于写作,一种用于阅读。读描述符总是跟在写描述符之后。读取后数据被丢弃。

echo a_command | another_command

有时,您无法在启动命令时创建管道。在这些情况下,您可以使用命名管道或 unix-sockets。

UNIX 套接字是通过其中一个进程创建和销毁的。这也是两种方式。

可以独立于两个进程创建命名管道。

答案2

zsh并且ksh93有用于系统调用的内置包装器lseek(),但没有 bash,因此您需要调用一个单独的实用程序来执行lseek()此操作打开文件描述附在fd上。zsh默认情况下安装在 macOS 上,因此,如果您要使用bash,您可以定义一个包装的内置sysseek函数:zshsysseek

sysseek() {
  zsh -c '
    zmodload zsh/system
    sysseek "$@" || syserror -p "$0: "
  ' sysseek "$@"
}

并使用:

sysseek -u 3 0

将 fd 3 倒回到开头。

对于没有的系统,另一个选择zsh是使用perl更广泛可用的:

sysseek() { # args: fd, mode, offset
  perl -e '
    my ($fd, $mode, $offset) = @ARGV;
    open FD, ">&", $fd or die "fd $fd: $!\n";
    seek FD, $mode, $offset or die "seek: $!\n";
  ' -- "$@"
}

并使用:

sysseek 3 0 0

将 fd 3 倒回到开头。

第二个参数可以是:

  1. 相对于开始位置进行查找(如sysseek -w startzsh,默认值)。
  2. 相对于当前位置 ( zsh's sysseek -w current)进行查找
  3. 相对于文件末尾 ( zsh's sysseek -w end) 进行查找。

答案3

将读描述符的创建与写描述符分开:

exec 3>file
echo foo >&3
exec 3<file
cat <&3
exec 3<&-

3 个描述符的表被覆盖,并且变为只读。使用通用描述符是没有意义的。

将描述符编号隐藏在变量中:

{ echo foo >&$fd; exec {fd}<&-; } {fd}<>file
{ cat <&$fd; } {fd}<>file
exec {fd}<&-

由于首先执行重定向,$fd变量已写入块内并具有值。这里应注意,句柄不会像 ksh93 中那样自动关闭,必须在块末尾或使用下一个命令明确关闭(显示了两个选项)。准备在下一个 bash 版本中引入内置变量,可以将其更改为自动关闭行为。使用这种形式的调用,不需要命令exec {fd}<&-

相关内容