在运行时替换 shell 脚本

Ere*_*ted 5 linux shell bash buffer

我有一个运行“补丁”脚本的电路板。补丁脚本始终在后台运行,它是一个运行以下伪代码的 shell 脚本:

while true; do
    # checks if a patch tar file exists and if yes then do patching
    sleep 10
done
Run Code Online (Sandbox Code Playgroud)

该脚本位于 /opt/patch.sh 并由 SystemV init 脚本启动。

问题在于,当脚本找到 tar 时,它会提取它,并且在里面有一个名为patch.sh的 shell 脚本,它特定于 tar 的内容。

/opt/patch.sh 中的脚本找到 tar 时,它会执行以下操作:

tar -xf /opt/update.tar -C /mnt/update
mv /mnt/update/patch.sh /opt/patch.sh
exec /opt/patch.sh
Run Code Online (Sandbox Code Playgroud)

它用另一个脚本替换自己并从同一位置执行它。这样做会出现任何问题吗?

ilk*_*chu 6

如果文件被就地写入替换(inode 保持不变),则任何打开该文件的进程在从该文件读取时都会看到新数据。如果通过取消旧文件的链接并创建一个同名的新文件来替换它,则索引节点号会发生变化,并且任何保持该文件打开的进程仍将拥有文件。

mv可能会执行任一操作,具体取决于文件系统之间是否发生移动...为了确保获得全新的文件,请首先取消链接或重命名原始文件。像这样的东西:

mv /opt/patch.sh /opt/patch.sh.old     # or rm
mv /mnt/update/patch.sh /opt/patch.sh
Run Code Online (Sandbox Code Playgroud)

这样,即使在移动之后,正在运行的 shell 仍将拥有旧数据的文件句柄。


也就是说,据我测试,Bash 在执行任何循环之前都会读取整个循环,因此只要执行保持在循环内,对底层文件的任何更改都不会改变正在运行的脚本。(它必须在执行之前读取整个循环,因为最后可能会有影响整个循环的重定向。)

退出循环后,Bash 将读取指针移回到循环结束后的位置,然后从循环结束后的位置继续读取输入文件。

脚本中定义的任何函数也会加载到内存中,因此将脚本的主要逻辑放入函数中,并且仅在最后调用它将使脚本非常安全,不会对文件进行修改:

#!/bin/sh
main() {
    do_stuff
    exit
}
main
Run Code Online (Sandbox Code Playgroud)

不管怎样,测试脚本被覆盖时会发生什么并不难:

$ cat > old.sh <<'EOF'
#!/bin/bash
for i in 1 2 3 4 ; do
        # rm old.sh
        cat new.sh > old.sh 
        sleep 1
        echo $i
done
echo will this be reached?
EOF
$ cat > new.sh <<'EOF'
#!/bin/bash
echo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
echo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
echo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
echo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
EOF
$ bash old.sh
Run Code Online (Sandbox Code Playgroud)

注释掉后rm old.sh,脚本将就地更改。如果没有注释,将创建一个新文件。(此示例部分依赖于new.sh大于old.sh,就好像它更短一样,shell 的读取位置将超过循环后新脚本的末尾。)