sed -i触摸它不会改变的文件

JNe*_*ill 6 ksh sed

我们服务器上的某个人运行sed -i 's/$var >> $var2/$var > $var2/ * 更改插入以覆盖公共目录中的某些bash脚本.没什么大不了的,它首先经过测试grep,它返回了预期的结果,只有他的文件会被触及.

他运行了脚本,现在文件夹中1400个文件的1200个文件有一个新的修改日期,但据我们所知,只有他的少量文件实际上已被更改.

  1. 为什么sed'触摸'一个它没有改变的文件.
  2. 为什么它只会"触摸"部分文件而不是所有文件.
  3. 它实际上是否改变了一些东西(可能是因为$sed正则表达式中的一些尾随空格或完全出乎意料的东西)?

Joh*_*024 10

当GNU sed成功编辑"就地"文件时,其时间戳会更新.为了理解原因,我们来看看如何编辑"就地":

  1. 创建一个临时文件来保存输出.

  2. sed 处理输入文件,将输出发送到临时文件.

  3. 如果指定了备份文件扩展名,则输入文件将重命名为备份文件.

  4. 无论是否创建备份,临时输出都会移动(rename)到输入文件.

GNU sed不会跟踪是否对文件进行了任何更改.无论临时输出文件中的什么内容都通过移动到输入文件rename.

这个过程有一个很好的好处:POSIX要求rename是原子的.因此,输入文件永远不会处于错位状态:它既可以是原始文件,也可以是修改后的文件,而不会介于两者之间.

作为此过程的结果,任何sed成功处理的文件都将更改其时间戳.

让我们考虑一下inputfile:

$ cat inputfile
this is
a test.
Run Code Online (Sandbox Code Playgroud)

现在,在监督下strace,让我们sed -i以保证不会导致任何变化的方式运行它:

$ strace sed -i 's/XXX/YYY/' inputfile
Run Code Online (Sandbox Code Playgroud)

编辑后的结果如下:

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
Run Code Online (Sandbox Code Playgroud)

如您所见,在文件句柄4上sed打开输入文件.inputfile然后./sediWWqLI在文件句柄6上创建一个临时文件来保存输出.它从输入文件中读取并将其未更改地写入输出文件.完成此操作后,将调用renameto overwrite inputfile,更改其时间戳.

GNU 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);
Run Code Online (Sandbox Code Playgroud)

ck_rename是stdio函数的封面函数rename.来源ck_renamesed/utils.c.

如您所见,没有标记来确定文件是否实际更改. rename被称为无论如何.

时间戳未更新的文件

至于其时间戳没有改变的1400个文件中的200个,这意味着sed这些文件以某种方式失败了.一种可能性是权限问题.

sed -i 和符号链接

正如mklement0所述,应用sed -i符号链接会产生令人惊讶的结果. sed -i 不更新符号链接指向的文件.而是使用新的常规文件sed -i 覆盖符号链接.

这是sed对STDIO 的调用的结果rename.如下所述man 2 rename:

如果newpath引用符号链接,则链接将被覆盖.

mklement0报告说sedMac OSX 10.10上的(BSD)也是如此.


cen*_*tic 7

我使用以下解决方法,即单独查看每个文件,使用grep检查文件是否包含字符串,然后使用sed.不是很好,但有效......

for i in *;do grep mytext $i && sed -i -e 's/mytext/replacement/g' $i;done
Run Code Online (Sandbox Code Playgroud)