我们服务器上的某人运行
sed -i 's/$var >> $var2/$var > $var2/ *
将公共目录中某些 bash 脚本中的插入更改为覆盖。没什么大不了的,首先用grep
进行了测试,它返回了预期的结果,只有他的文件会被触及。
他运行了脚本,现在文件夹中 1400 个文件中的 1200 个文件有了新的修改日期,但据我们所知,实际上只有他的一小部分文件被更改了。
$
而导致的一些完全意想不到的东西)?当 GNU
sed
成功“就地”编辑文件时,其时间戳会更新。要了解原因,让我们回顾一下“就地”编辑是如何完成的:
创建一个临时文件来保存输出。
sed
处理输入文件,将输出发送到临时文件。如果指定了备份文件扩展名,则输入文件将重命名为备份文件。
rename
) 到输入文件。GNU
sed
不会跟踪是否对文件进行了任何更改。临时输出文件中的任何内容都会通过 rename
移至输入文件。
这个过程有一个很好的好处:POSIX 要求
rename
是原子的。因此,输入文件永远不会处于损坏状态:它要么是原始文件,要么是修改后的文件,并且永远不会介于两者之间。
此过程的结果是,任何成功处理的文件
sed
都会更改其时间戳。
让我们考虑一下这个
inputfile
:
$ cat inputfile
this is
a test.
现在,在
strace
的监督下,让我们以保证不会造成任何变化的方式运行 sed -i
:
$ strace sed -i 's/XXX/YYY/' inputfile
编辑后的结果如下所示:
execve("/bin/sed", ["sed", "-i", "s/XXX/YYY/", "inputfile"], [/* 55 vars */]) = 0
[...snip...]
open("inputfile", O_RDONLY) = 4
[...snip...]
open("./sediWWqLI", O_RDWR|O_CREAT|O_EXCL, 0600) = 6
[...snip...]
read(4, "this is\na test.\n", 4096) = 16
write(6, "this is\n", 8) = 8
write(6, "a test.\n", 8) = 8
read(4, "", 4096) = 0
[...snip...]
close(4) = 0
[...snip...]
close(6) = 0
[...snip...]
rename("./sediWWqLI", "inputfile") = 0
如您所见,
sed
在文件句柄 4 上打开输入文件 inputfile
。然后,它在文件句柄 6 上创建一个临时文件 ./sediWWqLI
,以保存输出。它从输入文件中读取并将其不变地写入输出文件。完成此操作后,将调用 rename
来覆盖 inputfile
,更改其时间戳。
sed
源代码相关源码在
源码的
execute.c
目录下的sed
文件中。从版本 4.2.1 开始:
ck_fclose (input->fp);
ck_fclose (output_file.fp);
if (strcmp(in_place_extension, "*") != 0)
{
char *backup_file_name = get_backup_file_name(target_name);
ck_rename (target_name, backup_file_name, input->out_file_name);
free (backup_file_name);
}
ck_rename (input->out_file_name, target_name, input->out_file_name);
free (input->out_file_name);
ck_rename
是 stdio 函数 rename
的覆盖函数。 ck_rename
的来源位于 sed/utils.c
。
如您所见,没有保留任何标志来确定文件是否实际更改。
rename
无论如何都会被调用。
至于 1400 个文件中的 200 个时间戳没有改变,这意味着
sed
在这些文件上失败了。一种可能性是权限问题。
sed -i
和符号链接正如 mklement0 所指出的,将
sed -i
应用于符号链接会产生令人惊讶的结果。 sed -i
不更新符号链接指向的文件。相反,sed -i
用新的常规文件覆盖符号链接。
这是
sed
对 STDIO rename
进行调用的结果。正如man 2 rename
所记录:
如果 newpath 引用符号链接,则该链接将被覆盖。
mklement0 报告说,Mac OSX 10.10 上的 (BSD)
sed
也是如此。
我使用以下解决方法,即分别查看每个文件,使用 grep 检查文件是否包含该字符串,然后使用 sed。不太好,但可以用...
for i in *;do grep mytext "$i" && sed -i -e 's/mytext/replacement/g' "$i";done