sed 慎用 -i 参数

本文最后由 森林生灵 于 2020/05/05 13:10:42 编辑

文章目录 (?) [+]

    sed (Stream Editor, 文本流编辑器) 可谓是 shell 中必不可少一款文本处理工具了。我们经常会使用 sed -i 参数来对一些配置文件做自动化修改,但是此次在 docker 这样做中却遇到了问题

    sed: can't move '/etc/redis.confDhPCho' to '/etc/redis.conf': Resource busy

    docker-entrypoint.sh 启动部分如下,

    # first boot
    if [ ! -f /data/dump.rdb ]; then
        if [ -z "$REDIS_PASSWORD" ]; then
            REDIS_PASSWORD="$(pwgen 16 1)"
            echo "[INFO] Generated Redis Password: $REDIS_PASSWORD"
        fi
        sed -i "s/^# requirepass foobared$/requirepass $REDIS_PASSWORD/" /etc/redis.conf
    fi

    docker-compose.yml 部分配置文件如下,

    redis:
      build: ./redis
      image: docker-lnmp-redis:latest
      ports:
        - "6379:6379"
      networks:
        - backend
      volumes:
        - ./redis/redis.conf:/etc/redis.conf:rw
        - ./data/logs/redis/:/var/log/redis/:rw
        - ./data/redis/:/data/:rw
      environment:
        REDIS_PASSWORD: 123456
      restart: always
      container_name: redis

    根据启动文件可知由该镜像创建的容器首次启动会对 /etc/redis.conf 文件做修改,而在 docker-compose.yml 中配置外部文件映射到容器内 /etc/redis.conf,也即容器首次启动就会修改这个外部文件。

    然而 sed 并不给面子,抛出了一个异常,究其原因,在于 sed -i 此操作看似是修改了文件,但实际上为使用具有修改后的内容的新文件覆盖了原文件,这将导致文件的 Inode 发生变化,而在 docker 中挂载的文件是不允许这样做的。类似的问题还存在于 sed -i 修改符号文件。

    $ echo 123 > txt
    $ stat txt
      File: txt
      Size: 4               Blocks: 8          IO Block: 4096   regular file
    Device: fc01h/64513d    Inode: 1179795     Links: 1
    Access: (0664/-rw-rw-r--)  Uid: ( 1000/    choi)   Gid: ( 1000/    choi)
    Access: 2020-05-05 03:11:32.293005107 +0800
    Modify: 2020-05-05 03:11:32.293005107 +0800
    Change: 2020-05-05 03:11:32.293005107 +0800
     Birth: -
    
    # 使用 sed -i 修改文件 Inode 会变
    $ sed -i 's/123/abc/' txt
    $ stat txt
      File: txt
      Size: 4               Blocks: 8          IO Block: 4096   regular file
    Device: fc01h/64513d    Inode: 1179862     Links: 1
    Access: (0664/-rw-rw-r--)  Uid: ( 1000/    choi)   Gid: ( 1000/    choi)
    Access: 2020-05-05 03:12:42.571444788 +0800
    Modify: 2020-05-05 03:12:42.571444788 +0800
    Change: 2020-05-05 03:12:42.571444788 +0800
     Birth: -

    为了避免文件覆盖导致 Inode 变化,于是乎有了一个大胆的想法——使用重定向来重写文件内容

    $ echo 123 > txt
    $ stat txt
      File: txt
      Size: 4               Blocks: 8          IO Block: 4096   regular file
    Device: fc01h/64513d    Inode: 1179795     Links: 1
    Access: (0664/-rw-rw-r--)  Uid: ( 1000/    choi)   Gid: ( 1000/    choi)
    Access: 2020-05-05 03:14:10.738505467 +0800
    Modify: 2020-05-05 03:14:10.738505467 +0800
    Change: 2020-05-05 03:14:10.738505467 +0800
     Birth: -
    
    # 使用重定向修改不会导致文件 Inode 变化
    $ echo abc > txt
    $ stat txt
      File: txt
      Size: 4               Blocks: 8          IO Block: 4096   regular file
    Device: fc01h/64513d    Inode: 1179795     Links: 1
    Access: (0664/-rw-rw-r--)  Uid: ( 1000/    choi)   Gid: ( 1000/    choi)
    Access: 2020-05-05 03:14:10.738505467 +0800
    Modify: 2020-05-05 03:15:19.244883632 +0800
    Change: 2020-05-05 03:15:19.244883632 +0800
     Birth: -

    接着有了如下修改......

    sed "s/^# requirepass foobared$/requirepass $REDIS_PASSWORD/" /etc/redis.conf > /etc/redis.conf

    然鹅,正当沉浸在找到问题原因所在的欢乐中,现实再一次给了沉痛一击......

    是的,Inode 是没变,但是文件内容已经空空如也了!!!

    为什么会这样呢,原因在于 I/O 重定向时 stdout 与 stderr 的管道会先准备好再从 stdin 读入,也就是说 > file 时会先将 file 清空然后再读入数据,因此不等到 sed 读数据时文件已经就是空的了。

    # I/O 重定向会先清空文件
    $ echo 123 > txt
    $ cat < txt > txt
    $ cat test.txt

    正确做法如下,既保证了 Inode 不变又修改了文件内容

    $ echo 123 > txt
    # 注意不可以再将 tee txt >/dev/null 否则又是空
    # https://github.com/koalaman/shellcheck/wiki/SC2094
    $ cat txt | sed 's/123/abc/' | tee txt
    
    # 或
    $ sed 's/123/abc' txt > tmp && cat tmp > txt && rm tmp


    本文标题:sed 慎用 -i 参数
    本文链接:https://lanseyujie.com/post/careful-use-i-parameter-in-sed.html
    版权声明:本文使用「署名-非商业性使用-相同方式共享」创作共享协议,转载或使用请遵守署名协议。
    点赞 0 分享 0