从文件描述符读取失败

从文件描述符读取失败

这个问题是关于文件描述符的读写。请参见以下示例:

#!/bin/sh

file='somefile'

# open fd 3 rw
exec 3<> "$file"

# write something to fd 3
printf "%s\n%s\n" "foo" "bar" >&3

# reading from fd 3 works
cat "/proc/$$/fd/3"

# only works if the printf line is removed
cat <&3

exit 0

该脚本输出:

foo
bar

预期输出:

foo
bar
foo
bar

打开并写入文件描述符成功。通过阅读也是如此proc/$$/fd/3。但是这个不便于携带cat <&3不输出任何内容。但是,当未写入文件描述符时(例如取消注释该printf行),它会起作用。

为什么不起作用cat <&3以及如何从可移植的文件描述符(POSIX shell)读取整个内容?

答案1

cat <&3完全执行其应该执行的操作,即从文件中读取直到到达文件末尾。当您调用它时,文件描述符上的文件位置就是您上次离开它的位置,即文件末尾。 (有一个文件位置,而不是单独的用于读取和写入的位置。)

cat /proc/$$/fd/3不做同样的事情cat <&3:它在不同的描述符上打开相同的文件。由于每个文件描述符都有自己的位置,并且在打开文件进行读取时将位置设置为 0,因此该命令打印整个文件并且不会影响脚本。

如果你想读回你写的内容,你需要重新打开文件或倒回文件描述符(即将其位置设置为0)。在 POSIX shell 和大多数 sh 实现中都没有内置的方法可以执行此操作(ksh93中有一个)。只有一种实用程序可以寻求:dd,但只能向前寻求。 (还有其他实用程序可能会向前跳,但这没有帮助。)

我认为唯一的便携式解决方案是记住文件名并根据需要多次打开它。请注意,如果该文件不是常规文件,您可能无法向后查找。

答案2

#!/bin/sh

exec 3>file
exec 4<file

printf "%s\n" "foo" "bar" >&3
cat <&4

通过使用单独的文件描述符进行读取和写入,您可以在文件中获得单独的位置。书写不会改变阅读位置。

答案3

使用 ksh93,可以查找:

#!/usr/bin/env ksh93
file='somefile'
exec 3<> "$file"
printf "%s\n%s\n" "foo" "bar" >&3
cat "/proc/$$/fd/3"
exec 3>#((0))
cat <&3
exit 0

我得到:

foo
bar
foo
bar

如所愿。

答案4

如果您正在对您希望再次阅读的文件描述符进行一些工作,那么您可以在此处文档的正文中进行移植:

exec 4<somefile 3<<plus
$(    printf %s\\n some random lines
        cat <&4
)
plus
cat <&3

该解决方案可能并不完美 - 例如,没有可移植的方法来通过此处的文档文件描述符进行查找。命令替换将从cat写入标准输出的内容中删除尾随空白行,但所有这些问题都可以非常简单地处理。

首先,重定向的范围仅限于其包含的复合命令,因此将它们嵌套在循环中以处理您可能需要的任何重复是一件小事。还可以在命令子本身内实现循环 - 或者甚至可以根据/dev/tty需要调用交互式 shell。但最常见的是,我更喜欢将定界文档链接到函数定义。

fn() { : do something w/ fd3
} 3<<INPUT
$1
$(:gen some output;:maybe cat or head <stdin)
INPUT
while IFS= read -r line; do fn "$line"; done

也可以读取另一个heredoc所写的内容。

cat 4<<SAVED <<AND
$(  cat)
SAVED
$(  printf %s\\n some random lines
     cat <&4)
AND

或者你可以像这样循环...

until test && cat <&4
do exec 3<&4 4<<IN
$(: gen output; cat <&3)
IN
done 4<infile

当然,您fn也可以从内部调用类似上面的内容。

关于尾随空白行问题 -如果这是一个问题 - 那么你可以简单地echo .在命令 sub 的末尾,然后确保在你读它时从 fd 中删除最后一行sed \$d <&"$fd"

但是,虽然相当安全,但在 shell 中循环这样的文件通常是一个坏主意 - 请注意,每个生成的文件至少有一个分叉? Shell 分配 fd,实用程序处理它们 - 在sed或脚本中执行循环awk并使用标准实用程序操作文件数据,然后使用 shell 将其定向到输出 - 这通常是最佳实践。

相关内容