sed 原地替换和符号连接的一个小坑

今天遇到的一个小问题:foo.txt 是一个常规文件,link-1 是一个符号连接(有些地方又叫软连接,symbol link),指向的是 foo.txt,我们使用 sed -i"" 's/foo/bar/g'link-1 的内容进行替换,替换完成之后,发现 link-1 从一个符号连接变成了一个常规的文件。

实验流程如下,我们对 link-1 进行原地替换,-i"",执行之后,foo.txt 的内容没有改变,但是 link-1 从符号连接变成了常规文件:

乍一看,不合理,sed 只是用来替换一个文件,怎么能改变文件的属性呢?

但是仔细一想,其实挺合理:

  • sed 不可能同时打开一个文件,一边读一边写,因为一写的话,文件会被 truncate。比如执行一下 cat foo.txt > foo.txt,文件会被直接清空;
  • sed 也不可能将文件读入内存,处理,然后写入原文件。因为 sed 最基本的设计就是一个“行处理器”,要一行一行 streaming 处理,读入一部分,处理,然后写入一部分,用很少的内存就够了;
  • 所以要实现原地替换的话,就需要有一个临时文件,sed 先把结果写入到这个文件,最后将文件 rename 到原来的地方;

可以用 strace 来验证我们的推测:

可以看到,sed 就是打开了一个临时文件,然后读-处理-写,最后进行 rename(2)

使用 strace 跟踪 sed 的结果

那有没有不改变文件性质的方法呢?如果解析出来符号连接指向的目标就好了。

很多 Unix 命令,比如 cp, ls 都有设置 follow 符号连接还是不 follow 的选项,sed 应该也有。看了下 man sed,发现果然有。

       --follow-symlinks

              follow symlinks when processing in place

再用 strace 追踪了一下过程,发现 rename 的时候,就会指向符号连接的 target 而不是符号连接本身了。

strace 追踪 sed 执行过程


sed 原地替换和符号连接的一个小坑”已经有8条评论

  1. 您好,也就是说:
    1、sed 替换的底层逻辑是把更新后的内容写入一个临时文件里面,然后再重命名这个临时文件为原文件
    2、这样就会使得如果没有添加‘–follow-symlinks’选项的话,对软链接文件进行 sed 操作就会使得软链文件变成了一个常规文件(其实是那个临时文件重命名导致的)
    我这么理解对吗?

  2. 其实还有个 -c 选项,–follow-symlinks 针对软链接是适用的,对硬链接是不适用的,-c 对软硬链接都适用。按照 man 手册说法,如果不涉及硬链接,一般 –follow-symlinks 够了,性能较好。

回复 laixintao 取消回复

您的邮箱地址不会被公开。 必填项已用 * 标注