sed -i 触摸不改变的文件

问题描述 投票:0回答:2

我们服务器上的某人运行

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

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

  1. 为什么 sed 会“触摸”一个没有改变的文件。
  2. 为什么它只会“触及”文件的一部分而不是全部。
  3. 它实际上是否改变了某些东西(可能是一些尾随空格或由于 sed 正则表达式中的
    $
    而导致的一些完全意想不到的东西)?
sed ksh
2个回答
15
投票

当 GNU

sed
成功“就地”编辑文件时,其时间戳会更新。要了解原因,让我们回顾一下“就地”编辑是如何完成的:

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

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

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

  4. 无论是否创建备份,临时输出都会移动 (

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

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);

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
也是如此。


12
投票

我使用以下解决方法,即分别查看每个文件,使用 grep 检查文件是否包含该字符串,然后使用 sed。不太好,但可以用...

for i in *;do grep mytext "$i" && sed -i -e 's/mytext/replacement/g' "$i";done
© www.soinside.com 2019 - 2024. All rights reserved.