我们服务器上的某个人运行sed -i 's/$var >> $var2/$var > $var2/ *
来更改插入以覆盖公共目录中的某些bash脚本。没什么大不了的,它首先用grep
进行了测试,它返回了预期的结果,只有他的文件会被触及。
他运行了脚本,现在该文件夹中的1400个文件有一个新的修改日期,但据我们所知,只有他的少量文件实际上已被更改。
$
而导致的一些尾随空格或完全出乎意料的事情)?答案 0 :(得分:10)
当GNU sed
成功编辑文件"就地,"它的时间戳已更新。为了理解原因,让我们回顾一下编辑"就地"完了:
创建一个临时文件来保存输出。
sed
处理输入文件,将输出发送到临时文件。
如果指定了备份文件扩展名,则输入文件将重命名为备份文件。
无论是否创建备份,临时输出都会移动(rename
)到输入文件。
GNU sed
不会跟踪是否对文件进行了任何更改。临时输出文件中的任何内容都通过rename
移动到输入文件。
这个程序有一个很好的好处:POSIX requires that rename
be atomic。因此,输入文件永远不会处于错位状态:它既可以是原始文件,也可以是修改后的文件,而不会介于两者之间。
作为此过程的结果,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
源代码相关源代码位于the source 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
也是如此。
答案 1 :(得分:7)
我使用以下解决方法,即单独查看每个文件,使用grep检查文件是否包含字符串,然后使用sed。不是很好,但有效...
for i in *;do grep mytext $i && sed -i -e 's/mytext/replacement/g' $i;done