簡體   English   中英

來自 github 的自更新 bash 腳本

[英]Self updating bash script from github

我試圖讓我的腳本檢查我在 github 中的 repo 是否有更新,然后獲取更新並用新代碼替換舊代碼並運行新代碼“不是舊代碼”。 我想出了這個,但它完成后更新

self_update() {
    cd $(dirname $0)
    git fetch > a.txt 1> /dev/null 2> /dev/null
    git reset --hard >> a.txt 1> /dev/null 2> /dev/null
    git pull >> a.txt 1> /dev/null 2> /dev/null
    rm a.txt
    chmod +x "$(basename $0)"
    cd -
}
self_update
echo “some code”

編輯:我在stackoverflow 上找到了下面的代碼,它更新了我的腳本。 然而,它進入一個循環並且從不運行新的或舊的代碼,不知道為什么。

#!/bin/bash

SCRIPT=$(readlink -f "$0")
SCRIPTPATH=$(dirname "$SCRIPT")
SCRIPTNAME="$0"
ARGS="( $@ )"
BRANCH="master"

self_update() {
    cd $SCRIPTPATH
    git fetch

    [ -n $(git diff --name-only origin/$BRANCH | grep $SCRIPTNAME) ] && {
        echo "Found a new version of me, updating myself..."
        git pull --force
        git checkout $BRANCH
        git pull --force
        echo "Running the new version..."
        exec "$SCRIPTNAME" "${ARGS[@]}"

        # Now exit this old instance
        exit 1
    }
    echo "Already the latest version."
}
self_update
echo “some code”

重復輸出:

 Found a new version of me, updating myself...
 HEAD is now at 5dd5111 Update tool
 Already up to date
 Already on ‘master’
 Your branch is up to date with origin/master

它不會停止打印輸出,直到我 CTRL-C 輸出:執行方式:bash -x /opt/script/firstScript -h

++ readlink -f /opt/script/firstScript
+ SCRIPT=/opt/script/firstScript  
++ dirname /opt/script/firstScript
+ SCRIPTPATH=/opt/script                                
+ SCRIPTNAME=/opt/script/firstScript
+ ARGS='( -h )'
+ BRANCH=master
+ self_update      
+ cd /opt/script
+ git fetch                                                
++ git diff --name-only origin/master
++ grep /opt/script/firstScript
+ '[' -n ']'                                               
+ echo 'Found a new version of me, updating myself...'
Found a new version of me, updating myself...
+ git pull --force 
Already up to date.  
+ git checkout master
Already on 'master'
Your branch is up to date with 'origin/master'.
+ git pull --force
Already up to date.
+ echo 'Running the new version...'
Running the new version...
+ exec bash -x /opt/script/firstScript '( -h )'
++ readlink -f /opt/script/firstScript
+ SCRIPT=/opt/script/firstScript
++ dirname /opt/script/firstScript
+ SCRIPTPATH=/opt/script
+ SCRIPTNAME=/opt/script/firstScript
+ ARGS='( ( -h ) )'
+ BRANCH=master
+ self_update
+ cd /opt/script
+ git fetch
++ git diff --name-only origin/master
++ grep /opt/script/firstScript
+ '[' -n ']'
+ echo 'Found a new version of me, updating myself...'
Found a new version of me, updating myself...
+ git pull --force
Already up to date.
+ git checkout master
Already on 'master'
Your branch is up to date with 'origin/master'.
+ git pull --force
Already up to date.
+ echo 'Running the new version...'
Running the new version...
+ exec bash -x /opt/script/firstScript '( ( -h ) )'
++ readlink -f /opt/script/firstScript
+ SCRIPT=/opt/script/firstScript
++ dirname /opt/script/firstScript
+ SCRIPTPATH=/opt/script
+ SCRIPTNAME=/opt/script/firstScript
+ ARGS='( ( ( -h ) ) )'
+ BRANCH=master
+ self_update
+ cd /opt/script
+ git fetch
++ git diff --name-only origin/master
++ grep /opt/script/firstScript
+ '[' -n ']'
+ echo 'Found a new version of me, updating myself...'
Found a new version of me, updating myself...
+ git pull --force
Already up to date.
+ git checkout master
Already on 'master'
Your branch is up to date with 'origin/master'.
+ git pull --force
^C

輸出: 執行方式: bash /opt/script/firstScript -h

Found a new version of me, updating myself...
Already up to date.
Your branch is up to date with 'origin/master'.
Already up to date.
Running the new version...
Found a new version of me, updating myself...
Already up to date.
Your branch is up to date with 'origin/master'.
Already up to date.
Running the new version...
Found a new version of me, updating myself...
Already up to date.
Your branch is up to date with 'origin/master'.
Already up to date.
Running the new version...
Found a new version of me, updating myself...
Already up to date.
Your branch is up to date with 'origin/master'.
Already up to date.
Running the new version...
Found a new version of me, updating myself...
Already up to date.
Your branch is up to date with 'origin/master'.
Already up to date.
Running the new version...
Found a new version of me, updating myself...
Already up to date.
Your branch is up to date with 'origin/master'.
Already up to date.
Running the new version...
Found a new version of me, updating myself...
Already up to date.
Your branch is up to date with 'origin/master'.
Already up to date.
Running the new version...
Found a new version of me, updating myself...
^C

腳本無法自行更新的原因可能有多種。 暫時擱置“為什么”,考慮使用基於環境變量的“雙重調用”保護。 它將防止重復嘗試更新。

self_update() {
    [ "$UPDATE_GUARD" ] && return
    export UPDATE_GUARD=YES

    cd $SCRIPTPATH
    git fetch

    [ -n $(git diff --name-only origin/$BRANCH | grep $SCRIPTNAME) ] && {
        echo "Found a new version of me, updating myself..."
        git pull --force
        git checkout $BRANCH
        git pull --force
        echo "Running the new version..."
        exec "$SCRIPTNAME" "${ARGS[@]}"

        # Now exit this old instance
        exit 1
    }
    echo "Already the latest version."
}

此外,考慮更改self_update以返回到原始 cwd,如果它可能對運行腳本產生影響。

我懷疑您沒有為當前分支設置上游跟蹤。 然后git fetch什么也不做。 嘗試

git fetch origin master

相反(假設這是您想要的上游和分支)。

您似乎也不明白您找到的代碼中exec的重要性。 這將用更新的版本替換當前正在執行的腳本,並從頭開始運行它。 沒有辦法

update_code
echo "some stuff"

將在更新后立即echo "some stuff" 相反,它會再次執行自己,希望這次是更新版本的代碼。

然而,

[ -n $(git diff --name-only origin/$BRANCH | grep $SCRIPTNAME) ]

是一個非常迂回且可能脆弱的結構。 您問的是grep是否返回了任何(非空)輸出......但很明顯, grep本身是一個用於檢查是否有任何匹配項的工具。 此外,使用SCRIPTNAME這里是脆-如果腳本是用類似路徑調用/home/you/work/script/update_self_test這就是SCRIPTNAME包含但git diff只會輸出相對路徑( script/update_self_test在這種情況下,如果/home/you/work是 Git 工作目錄),因此grep將失敗。 (在您的bash -x成績單中,您會看到grep /opt/script/firstScript —— 這正是這個錯誤。)

既然你已經在文件目錄中了,我會提倡

git diff --name-only origin/master "$(basename "$SCRIPTNAME")"

如果文件沒有更改,它將不打印任何內容,如果更改,則打印此單個文件的文件名。 不幸的是,這並沒有將其退出代碼設置為指示成功或失敗(我想在這里很難定義,盡管傳統約定是常規diff命令在存在差異時報告非零退出代碼)但我們可以采用

git diff --name-only origin/master "$(basename "$SCRIPTNAME")" | grep -q . &&

為全條件。 (還要注意什么時候用引號將 shell 變量括起來?

最后,你自己的嘗試也有另一個問題。 除了未能exec ,您還有不明確的重定向。 您嘗試將內容發送到文件a.txt但您也嘗試將相同的內容發送到/dev/null 是哪個? 下定決心。

物有所值,

echo testing >a.txt 1>/dev/null

testing發送到/dev/null 它首先將標准輸出重定向到a.txt ,然后更新重定向,因此如果a.txt尚不存在,您只需在a.txt創建一個空文件即可。

最終,您可能還應該將私有變量切換為小寫; 但我看到您只是從另一個答案中復制了錯誤的約定。

閱讀 bash -x 輸出后,我可以給你重寫腳本

#!/bin/bash
                                               # Here I remark changes

SCRIPT="$(readlink -f "$0")"
SCRIPTFILE="$(basename "$SCRIPT")"             # get name of the file (not full path)
SCRIPTPATH="$(dirname "$SCRIPT")"
SCRIPTNAME="$0"
ARGS=( "$@" )                                  # fixed to make array of args (see below)
BRANCH="master"

self_update() {
    cd "$SCRIPTPATH"
    git fetch

                                               # in the next line
                                               # 1. added double-quotes (see below)
                                               # 2. removed grep expression so
                                               # git-diff will check only script
                                               # file
    [ -n "$(git diff --name-only "origin/$BRANCH" "$SCRIPTFILE")" ] && {
        echo "Found a new version of me, updating myself..."
        git pull --force
        git checkout "$BRANCH"
        git pull --force
        echo "Running the new version..."
        cd -                                   # return to original working dir
        exec "$SCRIPTNAME" "${ARGS[@]}"

        # Now exit this old instance
        exit 1
    }
    echo "Already the latest version."
}
self_update
echo “some code”

低於 1 . ARGS="( $@ )"肯定應該是ARGS=( "$@" ) ,否則在更新腳本執行后使用'( -h )'參數而不是-h (一般來說,它是在所有參數連接在單個字符串,即您將其作為/opt/script/firstScript -a -b -c運行,並在更新后作為/opt/script/firstScript '( -a -b -c )'

以下 2 . $(...)周圍需要雙引號,否則[ -n使用]作為輸入參數並返回 true 因為它不為空(而git-diff|grep的空輸出在[ -n參數列表中被忽略) (這是循環原因

基於這里的所有答案,以及我自己的一些要求:

  • 超時 git(!)
  • 靜音,無輸出
  • 隱藏本地更改
  • 遞歸循環陷阱
  • 始終使用 origin/main
  • 使用 git diff exitcodes 而不是輸出

我做了以下版本:

#!/bin/bash
                                               # Here I remark changes
#
SCRIPT="$(readlink -f "$0")"
SCRIPTFILE="$(basename "$SCRIPT")"             # get name of the file (not full path)
SCRIPTPATH="$(dirname "$SCRIPT")"
SCRIPTNAME="$0"
ARGS=( "$@" )                                  # fixed to make array of args (see below)

self_update() {
    [ "$UPDATE_GUARD" ] && return
    export UPDATE_GUARD=YES
    
    cd "$SCRIPTPATH"
    timeout 1s git fetch --quiet

                                               # in the next line
                                               # 1. added double-quotes (see below)
                                               # 2. removed grep expression so
                                               # git-diff will check only script
                                               # file

    timeout 1s git diff --quiet --exit-code "origin/main" "$SCRIPTFILE"
    [ $? -eq 1 ] && {
        #echo "Found a new version of me, updating myself..."
        if [ -n "$(git status --porcelain)" ];  # opposite is -z
        then 
            git stash push -m 'local changes stashed before self update' --quiet
        fi
        git pull --force --quiet
        git checkout main --quiet
        git pull --force --quiet
        #echo "Running the new version..."
        cd - > /dev/null                        # return to original working dir
        exec "$SCRIPTNAME" "${ARGS[@]}"

        # Now exit this old instance
        exit 1
    }
    #echo "Already the latest version."
}
self_update
echo "some code2"

您的腳本啟動 'self_update' bash 函數,它本身調用exec "$SCRIPTNAME" "${ARGS[@]}" 但如果我讀得好, $SCRIPTNAME就是你的腳本。

您的腳本將繼續遞歸調用自身。 這就是為什么你在循環。

您是否考慮過使用cron之類的東西運行腳本而不是讓它自稱?

編輯:還有測試中的 git 命令, git diff --name-only origin/$BRANCH如果您在其中進行了本地更改,則會以包含腳本文件的行進行響應,並且永遠循環。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM