如何使用 sed 就地编辑文件描述符

How to edit a file descriptor in place with sed

我成功地将文件描述符与 sed 结合使用,并在标准输出中给出了结果。提供一个包含 :

的文件 "file.txt"
$ cat file.txt
foo
Foo

我打开一个文件描述符给file.txt,打开一个子shell,然后把这个文件描述符给sed :

$ (sed "/Foo/c\bar" <&9 ) 9< file.txt
foo
bar

结果正确。

现在,如果我想使用 sed 的 -i 选项来改变位置,我有麻烦了。我以读写模式打开文件描述符,然后将其作为输入文件提供给 sed :

$ (sed -i "/Foo/c\bar" <&9 ) 9<> file.txt
sed: no input file

我不明白为什么缺少输入文件。也许 sed 在使用 -i 选项时需要文件名,而不是文件描述符?

我尝试了一种解决方法,当然,它没有按预期工作:

$ (sed "/Foo/c\bar" <&9 >&9 ) 9<> file.txt
$ cat file.txt
foo
Foo
foo
bar

虽然我预料到了:

$ cat file.txt
foo
bar

在此先感谢您的帮助! Dunatotatos

sed -i 需要一个文件名。你不能传递 /dev/stdin (或类似的),因为 sed 会尝试在 /dev.

中创建一个临时文件

您甚至无法将 sed 的输出保存到临时文件中,然后再次将输出写入文件描述符,因为您无法倒回 Bash 中的文件描述符。

你能做的就是从文件描述符中找出原始文件名。您可以使用 link /proc/self/fd/9 来执行此操作,如下所示:

sed -i "/Foo/c\bar" "$(readlink /proc/self/fd/9)"

但是请注意,原始文件可能已被删除或重命名,在这种情况下,此解决方案将不起作用。此外,此解决方案期望 /proc 可用,但情况可能并非总是如此。 /dev/fd/9 可能是一个很好的替代品。

另一件需要注意的事情是 sed -i 通过用新文件替换文件来工作:在 运行 sed -i 之后,您的 fd 9 将不会引用新文件创建的文件。要解决此问题:

name="$(readlink /proc/self/fd/9)"
cp "$name" "$name.tmp"
sed "/Foo/c\bar" "$name.tmp" > "$name"

这样,您的 fd 9 将在 运行 sed 前后引用相同的文件。您可能希望使用 mktemp 创建临时文件,并使用 atexit 确保它被删除。

您不能使用 sed 编辑任何内容 "in place",这是 -i 被错误命名的一个很好的例子。 gnu sed 通过创建一个新文件,将输出写入其中,然后重命名该文件来实现 -i。如果您不给 sed 原始文件名,它不知道要重命名什么。