運行時替換 shell 腳本

運行時替換 shell 腳本

我有一個正在運行“補丁”腳本的板。補丁腳本始終在後台運行,它是一個運行以下偽代碼的 shell 腳本:

while true; do
    # checks if a patch tar file exists and if yes then do patching
    sleep 10
done

腳本位於/opt/patch.sh,由SystemV init 腳本啟動。

問題是,當腳本找到 tar 時,它會提取它,並且裡面有一個名為的 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

它用另一個腳本替換自身並從同一位置執行它。這樣做會出現什麼問題嗎?

答案1

如果檔案被就地寫入取代(inode 保持不變),則任何開啟該檔案的進程會在從該檔案讀取時看到新資料。如果透過取消舊檔案的連結並建立一個同名的新檔案來取代它,則索引節點號會發生變化,並且任何保持該檔案開啟的程序仍將具有老的文件。

mv可能會執行任一操作,具體取決於檔案系統之間是否發生移動...為了確保獲得全新的文件,請先取消連結或重新命名原始文件。像這樣的東西:

mv /opt/patch.sh /opt/patch.sh.old     # or rm
mv /mnt/update/patch.sh /opt/patch.sh

這樣,即使在移動之後,正在運行的 shell 仍將擁有舊資料的檔案句柄。


也就是說,據我測試,Bash 在執行任何循環之前都會讀取整個循環,因此只要執行保持在循環內,對底層檔案的任何更改都不會改變正在運行的腳本。 (它必須在執行之前讀取整個循環,因為最後可能會影響整個循環的重定向。)

退出循環後,Bash 將讀取指標移回循環結束後的位置,然後從循環結束後的位置繼續讀取輸入檔。

腳本中定義的任何函數也會載入到記憶體中,因此將腳本的主要邏輯放入函數中,並且僅在最後調用它將使腳本非常安全,不會對檔案進行修改:

#!/bin/sh
main() {
    do_stuff
    exit
}
main

不管怎樣,測試腳本被覆蓋時會發生什麼並不難:

$ 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

註解掉後rm old.sh,腳本將就地更改。如果沒有註釋,將建立一個新文件。 (此範例部分依賴new.sh大於old.sh,就好像它更短一樣,shell 的讀取位置將超過循環後新腳本的末尾。)

答案2

我以前遇到過這個問題,並且可以確認這可能是一個問題。就我而言,回歸腳本首先執行 git pull,並可能在開始運行後進行更新,從而導致問題。

問題通常是 shell 會傳回並檢查是否還有更多行需要解釋。即使所需的程式碼位於循環內,這也可能會導致錯誤。為了避免這種情況,請使用以下結構這個帖子

答案3

一個自動執行、自動修改的腳本?這不是一個好主意。

更好的解決方案是創建一個具有最少功能的存根守護程序(即負責安裝新版本的從屬腳本並定期調用它)。類似...(未測試)

while true; do
  # check if a patch tar file exists and if yes then do patching
  if [ -f "$PATCH" ]; then
      ( cd /usr/local/mydaemon \
      && tar -xzf "$PATCH" \
      && rm -f "$PATCH" ) \
      || exit -1
  fi
  $SCRIPT
  sleep 10
done

相關內容