Shell script that updates itself


Recently I needed to write a shell script that updates itself, and, surprising, I found it an easy job to do. I will share the recipe here.

In my use case, I’m developing a kind of software updater and, before updating the system packages, I need to check if there is a new version of this software updater. If there is, then I update myself and run my new copy on-the-fly.

Enough talk, show me the code. I’ll paste here a simple shell script that talks by itself:

#!/bin/sh

SCRIPT_NAME="$0"
ARGS="$@"
NEW_FILE="/tmp/blog.sh"
VERSION="1.0"

check_upgrade () {

  # check if there is a new version of this file
  # here, hypothetically we check if a file exists in the disk.
  # it could be an apt/yum check or whatever...
  [ -f "$NEW_FILE" ] && {

    # install a new version of this file or package
    # again, in this example, this is done by just copying the new file
    echo "Found a new version of me, updating myself..."
    cp "$NEW_FILE" "$SCRIPT_NAME"
    rm -f "$NEW_FILE"

    # note that at this point this file was overwritten in the disk
    # now run this very own file, in its new version!
    echo "Running the new version..."
    $SCRIPT_NAME $ARGS

    # now exit this old instance
    exit 0
  }

  echo "I'm VERSION $VERSION, already the latest version."
}

main () {
  # main script stuff
  echo "Hello World! I'm the version $VERSION of the script"
}

check_upgrade
main

To try this script:
1) save it somewhere
2) save a copy of it at /tmp/blog.sh (as pointed at line 5)
3) modify the variable “VERSION” (line 6) of that copy, to, say, “2.0”.
4) run the original script (not the one at /tmp)

You will see that the script updated itself and ran the “new” 2.0 version.

Try running again the original script (step 4 above). See the difference? It doesn’t update itself anymore, because it is the “latest” version.

A small thing you might notice: at line 19, I deleted the “new file”. That’s merely for this educational example, that we check if there’s a new version of the script by just checking if a file exists in the disk. On real life (with apt/yum or any smarter process) this is not needed as our check for a new version (line 13) will naturally fail.

This was tested with bash, dash and busybox’s ash. Worked fine.

I hope it’s useful to someone. Comments are welcome!

18 thoughts on “Shell script that updates itself”

  1. In response to #1 and #2: better yet, exec the new copy of the script.

    Also, you might consider adding a simple example of signature validation; not that hard to do if gpg is installed.

  2. Jonh, my point was that you should never modify a script file – or executable – while it’s in use. Default behavior of (coreutils) ‘cp’ is to modify the destination file in place if it exists already. It’s much safer to replace it by a new file, which is what ‘cp –remove-destination’ and ‘install’ both do.

  3. In this case, the shell script is just a text file that was parsed by the shell binary (ash, bash, etc). Once the shell parsed it and is running it, I believe it’s not a problem to replace the text script file itself.

    Also, that cp option is not portable (at least in embedded systems).

  4. Guys, I’m happy we are having this discussion, that was my intention while writing this post.

    Sorry, but I still didn’t understand why changing from ‘cp’ to ‘mv’ in the example above would avoid bad things to happen.

    Assuming the text file is in the bash’s (or dash’s or …) memory, it’s not a problem at all, right?

    Assuming it’s not, like “Anonymous” said above, either command (cp, cp –remove, install, mv) would lead to some corruption, right?

    In my actual use case, I’m not issuing any of those commands, I’m relying on opkg (kind of a mini apt-get, targeting embedded systems).

  5. If the script is in memory, it’s not a problem, but in general there is no guarantee that this is the case.

    If the script is not yet in memory, cp may lead to corruption, but cp –remove-destination, install, and mv are safe as they never modify the old file. They all explicitly or implicitly unlink the old file and create or move a new file into its place. There is no connection between the unmodified contents of the old file and the new file. An unlinked file can still be open and used without corruption or other issues (on POSIX).

  6. Ah nice .. well i did a similar thing some years ago to keep my .bashrc up to date from a central copy on some dir. This piece of bash script will update ~/.bashrc from /nirvana/_bashrc everytime when there is a difference between the running one an the external one, copying it over to the current file, running (as in sourcing) it an then exit, to avoid running the old bashrc… 😉

    –[snip]–
    BASHRCOLD=~/.bashrc
    BASHRCNEW=/nirvana/_bashrc
    if [ -f “${BASHRCNEW}” ] ; then
    if ! cmp -s ${BASHRCOLD} ${BASHRCNEW} ; then
    /bin/cp -f ${BASHRCNEW} $BASHRCOLD
    source ${BASHRCNEW}
    return
    fi
    fi
    –[snap]–

    Have fun 😉

  7. Also, you could change line 24:

    $SCRIPT_NAME $ARGS

    to:

    $SCRIPT_NAME $ARGS &

    So that the old script exits immediatelly instead of stay there waiting for the new version to finish.

Comments are closed.