Self updating bash script from github

烈酒焚心 提交于 2020-01-23 17:11:12

问题


I am trying to make my script to check if there is an update from my repo in github and then get the updates and replace old code with new one and run the new code “not the old one”. I came up with this but it updates after it finishes

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”

Edit: I found the below code here stackoverflow and it updates my script. However, it goes into a loop and never runs the new or old code, not sure why.

#!/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”

Repeated Output:

 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

It does not stop printing the output till i CTRL-C Output: Executed with: 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

Output: Executed with: 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

回答1:


There could be multiple reasons that the script might fail to update itself. Putting aside the "why" for a minute, consider using an "double-invoke" guard based on environment variable. It will prevent repeated attempt to update.

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."
}

Also, consider changing the self_update to go back into the original cwd, if it might have impact on running the script.




回答2:


My suspicion is that you have not set up upstream tracking for the current branch. Then git fetch does nothing at all. Try

git fetch origin master

instead (assuming that's the upstream and branch you want).

You also don't seem to understand the significance of the exec in the code you found. This will replace the currently executing script with the updated version and start running it from the start. There is no way that

update_code
echo "some stuff"

will echo "some stuff" right after updating itself. Instead, it will execute itself again, hopefully then this time with an updated version of the code.

However,

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

is a really roundabout and potentially brittle construct. You are asking whether grep returned any (non-empty) output ... but obviously, grep itself is a tool for checking whether there are any matches. Additionally, using SCRIPTNAME here is brittle -- if the script was invoked with a path like /home/you/work/script/update_self_test that's what SCRIPTNAME contains, but git diff will only output a relative path (script/update_self_test in this case if /home/you/work is the Git working directory) and so the grep will fail. (In your bash -x transcript, you see grep /opt/script/firstScript -- that's exactly this bug.)

Since you are in the directory of the file already, I would advocate

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

which will print nothing if the file has not changed, and the file name of this single file if it has. Unfortunately, this does not set its exit code to indicate success or failure (I guess it's hard to define that here, though the traditional convention is for the regular diff command to report a non-zero exit code when there are differences) but we can use

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

for the whole conditional. (Notice also When to wrap quotes around a shell variable?)

Finally, your own attempt has another problem as well. Besides failing to exec, you have ambiguous redirects. You try to send stuff to the file a.txt but you are also trying to send the same stuff to /dev/null. Which is it? Make up your mind.

For what it's worth,

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

sends testing to /dev/null; it first redirects standard output to a.txt but then updates the redirect, so you simply end up creating an empty file in a.txt if it didn't already exist.

Eventually, you should probably also switch to lower case for your private variables; but I see that you simply copied the wrong convention from the other answer.




回答3:


After reading bash -x output I can give you a rewrite of the script

#!/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”

Below 1. ARGS="( $@ )" definitely should be ARGS=( "$@" ), otherwise after update script is executed with '( -h )' argument instead of -h (in general, it is executed with all arguments concatenated in a single string, i.e. you run it as /opt/script/firstScript -a -b -c, and after update it runs as /opt/script/firstScript '( -a -b -c )'

Below 2. Double quotes are necessary around $(...), otherwise [ -n uses ] as input argument and returns true because it is not empty (while the empty output of git-diff|grep is ignored in argument list of [ -n) (That was the loop cause)




回答4:


Your script launches the 'self_update' bash function, whitch itself calls exec "$SCRIPTNAME" "${ARGS[@]}". But if I read well, $SCRIPTNAME is your script.

Your script will keep calling itself recursively. That's why you are looping.

Have you considered running your script with something like cron instead of making it call itself ?

Edit: Also the git command in the test, git diff --name-only origin/$BRANCH would respond with line containing the script file if you had local changes in it, and loop forever.



来源:https://stackoverflow.com/questions/59727780/self-updating-bash-script-from-github

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!