繁体   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