文件的“就地”修改(例如通过sed -i
或)是什么perl -i
意思?
我的问题是如何完成这种就地修改。是不是把文件复制过来,在副本中进行修改,然后替换原来的?或者原始文件是否被适当修改?
答案1
sed
创建一个临时文件,将输出写入该文件,然后在原始文件的基础上重命名该临时文件。
您可以使用以下方式观察发生的情况strace
:
$ strace -e trace=file sed -i -e '' a
execve("/usr/bin/sed", ["sed", "-i", "-e", "", "a"], [/* 34 vars */]) = 0
<...trimmed...>
open("a", O_RDONLY) = 3
open("./sedxvhRY8", O_RDWR|O_CREAT|O_EXCL, 0600) = 4
rename("./sedxvhRY8", "a") = 0
+++ exited with 0 +++
这会记录所有文件操作sed
:它创建一个新文件(安全地使用O_CREAT|O_EXCL
),将数据写入其中,然后将其移回原始文件的顶部a
。
sed -i
接受用于备份的后缀,在这种情况下,它首先将原始文件移开(而不是在顶部重命名)。该参数在大多数 BSD 中都是强制性的sed
。在这种情况下,目录中会短暂地不存在正确名称的文件。
perl
在最新版本中打开输入文件,然后将其删除并创建一个同名的新文件:
open("a", O_RDONLY) = 3
unlink("a") = 0
open("a", O_WRONLY|O_CREAT|O_EXCL, 0600) = 4
当您删除 (unlink
)您已经打开的文件,只要保留句柄,您就可以保留对它的访问权限,因此它可以继续从已删除的文件中读取数据。通过这种方式,perl
直接写入输出文件,而不是写入临时文件:不会创建额外的文件,但如果您在此过程中读取文件,您将获得部分内容,这与 withsed
的方法不同。还有一段短暂的时间没有名称正确的文件,这是在进程的开始而不是结束时(如 中所示sed -i .bak
)。
两者sed
和perl
都会:
- 将符号链接替换为普通文件。
- 断开硬链接。
- 如果可能,保留组所有权。
setgid
如果该文件属于您不在其中且您不是 root 的组,则使用您的默认组(或父目录的组,如果该目录具有该位)创建该文件。- 如果您是 root,请保留文件所有权。
- 保留基本权限。
- 保存
setuid
和setgrp
位,如果结果组与其开始时所在的组相同。 - 保留粘性部分。
- 不是保留xattrs。
sed
将要:
- 保留 ACL (在 Linux 上;我不知道其他的)。
perl
将要:
- 不是保留 ACL。
上述情况在带有 GNU 的 Linuxsed
和带有其(FreeBSD 派生的)的 Mac OS X上都是正确的sed
。
答案2
除了@Homer 的回答之外,来自perldoc perlrun
:
指定要就地编辑由“<>”构造处理的文件。它通过重命名输入文件、按原始名称打开输出文件并选择该输出文件作为 print() 语句的默认文件来实现此目的。扩展名(如果提供)用于修改旧文件的名称以制作备份副本,遵循以下规则:
如果未提供扩展名,则不会进行备份并覆盖当前文件。
如果扩展名不包含*,则它将作为后缀附加到当前文件名的末尾。如果扩展名确实包含一个或多个 * 字符,则每个 * 都会替换为当前文件名。
请记住,不会保留软链接或硬链接:
请注意,由于 -i 在创建同名新文件之前重命名或删除原始文件,因此不会保留 UNIX 风格的软链接和硬链接。
最后,当命令行上没有给出文件时,-i 开关不会妨碍执行。在这种情况下,不会进行备份(当然无法确定原始文件),并且处理过程如预期的那样从 STDIN 继续到 STDOUT。
这也解释了为什么您必须使用-i
with-p
选项,或者print
如果您想就地编辑 with ,则使用显式语句perl
:
# Opps, file will be truncated, becomes empty
$ perl -i.bak -ne 's/123/qwe/' file
# Right way
$ perl -i.bak -ne 's/123/qwe/;print' file
# Or
$ perl -i.bak -pe 's/123/qwe/' file