我正在编写一个已经变得如此复杂的脚本我想要包含一个简单的选项来将其更新到最新版本.这是我的方法:
set -o errexit
SELF=$(basename $0)
UPDATE_BASE=http://something
runSelfUpdate() {
echo "Performing self-update..."
# Download new version
wget --quiet --output-document=$0.tmp $UPDATE_BASE/$SELF
# Copy over modes from old version
OCTAL_MODE=$(stat -c '%a' $0)
chmod $OCTAL_MODE $0.tmp
# Overwrite old file with new
mv $0.tmp $0
exit 0
}
Run Code Online (Sandbox Code Playgroud)
该脚本似乎按预期工作,但我想知道是否可能有这种方法的警告.我很难相信脚本可以覆盖自己而不会产生任何影响.
为了更清楚,我想知道,如果,也许,bash会逐行读取和执行脚本,之后mv,exit 0可能是新脚本中的其他内容.我想我记得Windows的行为和.bat文件一样.
更新:我的原始片段不包括set -o errexit.根据我的理解,这应该让我免受由此引起的问题wget.
此外,在这种情况下,UPDATE_BASE指向版本控制下的位置(以缓解问题).
结果:根据这些答案的输入,我构建了这个修订的方法:
runSelfUpdate() {
echo "Performing self-update..."
# Download new version
echo -n "Downloading latest version..."
if ! wget --quiet --output-document="$0.tmp" $UPDATE_BASE/$SELF ; then
echo "Failed: Error while trying to wget new version!"
echo "File requested: $UPDATE_BASE/$SELF"
exit 1
fi
echo "Done."
# Copy over modes from old version
OCTAL_MODE=$(stat -c '%a' $SELF)
if ! chmod $OCTAL_MODE "$0.tmp" ; then
echo "Failed: Error while trying to set mode on $0.tmp."
exit 1
fi
# Spawn update script
cat > updateScript.sh << EOF
#!/bin/bash
# Overwrite old file with new
if mv "$0.tmp" "$0"; then
echo "Done. Update complete."
rm \$0
else
echo "Failed!"
fi
EOF
echo -n "Inserting update process..."
exec /bin/bash updateScript.sh
}
Run Code Online (Sandbox Code Playgroud)
(至少它不会尝试在更新后继续运行!)
这让我担心你的方法的事情是,你会覆盖当前脚本(mv $0.tmp $0),因为它的运行.有许多的原因,这将可能是工作,但我不会赌大量在于能够保证在任何情况下工作.我不知道POSIX或任何其他标准中的任何内容,它指定shell如何处理它作为脚本执行的文件.
以下是可能会发生的事情:
你执行脚本.内核看到了这#!/bin/sh行(你没有显示它,但我认为它就在那里)并/bin/sh以脚本的名称作为参数调用.然后fopen(),shell使用或者open()打开脚本,从中读取,并开始将其内容解释为shell命令.
对于一个足够小的脚本,shell可能只是显式地或者作为普通文件I/O完成的缓冲的一部分将整个内容读入内存.对于更大的脚本,它可能会在执行时以块的形式读取它.但无论哪种方式,它可能只打开文件一次,并且只要它正在执行就保持打开状态.
如果删除或重命名文件,则不一定会立即从磁盘中删除实际文件.如果有另一个硬链接,或者某个进程打开它,则该文件仍然存在,即使另一个进程可能无法再使用相同的名称打开它,或者根本不可能.在删除引用它的最后一个链接(目录条目)之前,文件不会被物理删除,并且没有进程将其打开.(即便如此,它的内容也不会立即被删除,但这超出了相关内容.)
此外,mv紧跟着脚本文件的命令紧随其后exit 0.
但至少可以想象shell可以关闭文件然后按名称重新打开它.我想不出有什么好的理由这样做,但我知道它不会绝对保证.
有些系统倾向于对大多数Unix系统进行更严格的文件锁定.例如,在Windows上,我怀疑mv命令会失败,因为进程(shell)打开了文件.您的脚本可能在Cygwin上失败.(我没试过.)
所以让我感到紧张的不是它可能失败的可能性小,而是一条长而微弱的推理线,这似乎表明它可能会成功,而且真正的可能性是我还没有想到的其他东西.
我的建议:编写第二个脚本,其唯一的工作是更新第一个脚本.将runSelfUpdate()函数或等效代码放入该脚本中.在原始脚本中,用于exec调用更新脚本,以便在更新原始脚本时不再运行该脚本.如果您想避免维护,分发和安装两个单独脚本的麻烦.您可以让原始脚本创建具有唯一的更新脚本/tmp; 这也将解决更新更新脚本的问题.(我不担心清理自动生成的更新脚本/tmp;那只会重新打开同样的蠕虫.)