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,发现果然有。

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

strace 追踪 sed 执行过程


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

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

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

Leave a comment

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