问题
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