w
我的命令sed
(macOS 13.1 的 sed)似乎能够使用cat
(bash 3.2)编辑输入文件:
printf "hello\nworld\n" > foo.txt
cat foo.txt | sed 's/l/L/g' | sed -n 'w foo.txt'
cat foo.txt
> heLLo
> worLd
我看了看https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html但我不确定为什么上面的管道可以成功编辑foo.txt
,这与使用重定向等时不同cat foo.txt | sed 's/l/L/g' > foo.txt
。
我知道我可以使用 POSIX-nonspecified标志或临时文件,但我想知道使用(写入)命令编辑输入文件-i
是否安全。w
编辑:
我试过
printf "%d hello world\n" {1..100000} > foo.txt
cat foo.txt | sed 's/l/L/g' | sed -n 'w foo.txt'
并发现它不再正常工作。结果foo.txt
只有 4000-8000 行。
答案1
使用sponge
(来自更多实用程序,或者重定向到临时文件并将其重命名为原始文件,或者使用编辑(或ex
来自 vi/vim/nvi)而不是sed
- 请记住,这sed
是面向流的版本ed
。 ed
= editor
, sed
=溪流编辑。
仅供参考:ed、sed 和 ex(还有 vi - vi 最初是作为六ed 的sual 版本)都共享一个公共的命令子集,因为它们都有共同的根......但它们中的每一个都是朝不同的方向开发的,并且具有不同的增强功能。每个都有多个不同的版本,同样具有不同的功能。许多其他程序至少借用了一些常用命令(例如,rogue 和 nethack 都借用了 hjkl 移动键)。另外值得注意的是,以防不明显:ex
命令是:
vi 内的命令,并且是命令的超集ed
(取决于vi
您使用的实现)。
所有三种方法的示例。
sed -e 's/l/L/g' foo.txt | sponge foo.txt
sed -e 's/l/L/g' foo.txt > foo.new && mv foo.new foo.txt
printf '%s\n' %s/l/L/g w q | ed -s foo.txt
printf '%s\n' %s/l/L/g w q | ex foo.txt
顺便说一句,来自man sponge
:
sponge
读取标准输入并将其写入指定文件。与 shell 重定向不同,sponge 在写入输出文件之前吸收所有输入。这允许构建读取和写入同一文件的管道。如果输出文件已经存在,海绵会保留该文件的权限。
笔记:
Sponge 本质上是重定向和重命名方法的便捷工具。
重定向和重命名不保留原始输出文件的权限。它创建一个新文件,其权限由用户确定
umask
(与创建的任何其他新文件一样) - 根据 umask,这些权限可能与原始权限相同,也可能不同。不同之处在于
sponge
确保新文件具有与原始文件相同的权限,而简单的重定向则不然。使用
ed
and 时ex
,每个命令(s///
替换w
为 write,最后替换q
为 quit)每行都会打印一个,printf '%s\n'
并通过管道输送到ed
orex
,它打开 foo.txt 并执行命令。
另请注意:ed
并且ex
两者都会覆盖原始文件(保留原始文件的索引节点号,因此不会破坏该文件的任何硬链接)。 sponge
和 write-to-a-tempfile-and-rename 都会创建具有不同 inode 编号的新文件,这将破坏任何硬链接。大多数时候(即除非您有一个或多个文件的硬链接),这根本不重要,但您需要注意这一点。
例如:注意 inode 号如何随以下变化sponge
:
$ printf "hello\nworld\n" > foo.txt
$ ls -li foo.txt
2251637 -rw-rw-r-- 1 cas cas 12 Feb 6 18:07 foo.txt
$ sed -e 's/l/L/g' foo.txt | sponge foo.txt
$ ls -li foo.txt
2251985 -rw-rw-r-- 1 cas cas 12 Feb 6 18:07 foo.txt
再次使用重定向覆盖文件不会更改 inode 编号,也不会使用 ex (或 ed)编辑它:
$ printf "hello\nworld\n" > foo.txt
$ ls -li foo.txt
2251985 -rw-rw-r-- 1 cas cas 12 Feb 6 18:08 foo.txt
$ printf '%s\n' %s/l/L/g w q | ex foo.txt
$ ls -li foo.txt
2251985 -rw-rw-r-- 1 cas cas 12 Feb 6 18:09 foo.txt
如果需要,您可以使用重定向和重命名方法保留原始索引节点,如下所示:
sed -e 's/l/L/g' foo.txt > foo.new
cat foo.new > foo.txt
rm foo.new
是的,我知道cat
不需要。<
重定向也有效。我发现(在命令行开头重定向,或没有实际命令的重定向)是令人厌恶的丑陋,并且没有恐惧或羞耻联合大学
而且,正如斯蒂芬·基特在评论中指出的那样,cp foo.new foo.txt
它也可以工作并且还保留原始权限。
答案2
该w
sed
命令在第一次调用时(此处是在sed
从管道读取数据块后处理其第一行时)使用 打开输出文件O_WRONLY | O_TRUNC
,因此此时该文件被清空(截断ated),所以如果正在读取文件的命令(在您的情况下cat
尚未完成读取),它将无法读取其余部分。
相反,你可以这样做:
sed 's/l/L/g' < file 1<> file
shell 在 sed 的 stdin 上使用O_RDONLY
和独立地在 sed 的 stdout 上使用 来打开文件O_RDWR
,但更重要的是,如果不使用O_TRUNC
sosed
将覆盖其自己的输入。
仅当sed
像这里一样总是写入与其读取的行大小(以字节数为单位)完全相同的输出行时,这才有效,否则它最终可能会覆盖尚未读取的行。
如果写入的内容比读取的内容短,它也会在文件末尾留下旧数据。可以通过调用在末尾截断标准输出的内容来解决这个问题,例如:
{ sed 's/hello/hi/g'; perl -e 'truncate STDOUT, tell STDOUT'; } < file 1<> file
但如果您要使用perl
,您不妨使用-i
一些sed
实现已复制的它:
perl -pi -e 's/hello/hi/g' file