![从文件描述符读取失败](https://linux22.com/image/47580/%E4%BB%8E%E6%96%87%E4%BB%B6%E6%8F%8F%E8%BF%B0%E7%AC%A6%E8%AF%BB%E5%8F%96%E5%A4%B1%E8%B4%A5.png)
这个问题是关于文件描述符的读写。请参见以下示例:
#!/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 将其定向到输出 - 这通常是最佳实践。