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

时间:2014-11-21 21:54:13

标签: sed ksh

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

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

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

2 个答案:

答案 0 :(得分:10)

当GNU sed成功编辑文件"就地,"它的时间戳已更新。为了理解原因,让我们回顾一下编辑"就地"完了:

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

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

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

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

  5. 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,从而更改其时间戳。

    GNU 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