2

/home/deploy/backup-scripts/utils.sh: line 41: $2: unbound variable

I cannot figure this out from the Bitbucket backups scripts...can anyone help?

When I use this, another script calls it using source. I cannot figure out why the variable is unbound when I call it with source from another bespoke script I created and it works fine when called from Bitbuckets own scripts?. Is there a bash issue where you cannot use source from a parent script and then again from within a child script?

# -------------------------------------------------------------------------------------
# Common utilities for logging, terminating script execution and Hipchat integration.
# -------------------------------------------------------------------------------------

# Terminate script execution with error message
function bail {
    error "$*"
    print_stack_trace
    exit 99
}

# Test for the presence of the specified command and terminate script execution if not found
function check_command {
    type -P "$1" &> /dev/null || bail "Unable to find $1, please install it and run this script again"
}

# Log an debug message to the console if BITBUCKET_VERBOSE_BACKUP=true
function debug {
    if [ "${BITBUCKET_VERBOSE_BACKUP}" = "true" ]; then
        print "$(script_ctx)[$(hostname)] DEBUG: $*"
    fi
}

# Log an error message to the console and publish it to Hipchat
function error {
    # Set the following to have log statements print contextual information
    echo "$(script_ctx)[$(hostname)] ERROR: $*" > /dev/stderr
    hc_announce "[$(hostname)] ERROR: $*" "red" 1
}

# Log an info message to the console and publish it to Hipchat
function info {
    # Set the following to have log statements print contextual information
    print "$(script_ctx)[$(hostname)]  INFO: $*"
    hc_announce "[$(hostname)]  INFO: $*" "gray"
}

# Checks if a variable is zero length, if so it prints the supplied error message and bails
function check_config_var {
    local conf_var_name="$1"
    local conf_error_message="$2"
    local conf_bail_message="$3"

    if [ -z "${conf_error_message}" ]; then
        conf_error_message="The configuration var '${conf_var_name}' is required, please update '${BACKUP_VARS_FILE}'."
    fi
    if [ -z "${conf_bail_message}" ]; then
        conf_bail_message="See bitbucket.diy-backup.vars.sh.example for the defaults and instructions."
    fi

    check_var "${conf_var_name}" "${conf_error_message}" "${conf_bail_message}"
}

# Similar to check_config_var but does does not print the extra message about consulting the vars file
function check_var {
    local set_var_name="$1"
    local set_error_message="$2"
    local set_bail_message="$3"

    if [ -z "${!set_var_name}" ]; then
        if [ -z "${set_error_message}" ]; then
            set_error_message="Fatal error '${set_var_name}' has not been set"
        fi
        if [ -z "${set_bail_message}" ]; then
            bail "${set_error_message}"
        else
            error "${set_error_message}"
            bail "${set_bail_message}"
        fi
    fi
}

# A function with no side effects. Normally called when a callback does not need to do any work
function no_op {
    echo > /dev/null
}

# Log a message to the console without adding standard logging markup
function print {
    echo "$@"
}

function script_ctx {
    if [ -n "${BASH_VERSION}" ]; then
        local depth=0
        for func in ${FUNCNAME[@]}; do
            case "${func}" in
                debug|info|error|bail|check_config_var|check_var|run|script_ctx|print_stack_trace)
                    depth=$((${depth}+1))
                ;;
            esac
        done
        echo "[$(basename ${BASH_SOURCE[${depth}]}):${BASH_LINENO[${depth}]} -> ${FUNCNAME[${depth}]}]"
    fi
}

function print_stack_trace {
    if [ -n "${BASH_VERSION}" ]; then
        local idx=0
        local depth=" "
        echo "Stack trace:" > /dev/stderr
        for func in ${FUNCNAME[@]}; do
            case "${func}" in
                debug|info|error|bail|check_config_var|check_var|run|script_ctx|print_stack_trace)
                ;;
            *)
                echo "${depth}[${BASH_SOURCE[${idx}]}:${BASH_LINENO[${idx}]} -> ${FUNCNAME[${idx}]}]" > /dev/stderr
                ;;
            esac
            depth="${depth} "
            idx=$((${idx}+1))
        done
    fi
}

# Log then execute the provided command
function run {
    if [ "${BITBUCKET_VERBOSE_BACKUP}" = "true" ]; then
        local cmdline=
        for arg in "$@"; do
            case "${arg}" in
                *\ * | *\"*)
                    cmdline="${cmdline} '${arg}'"
                    ;;
                *)
                    cmdline="${cmdline} ${arg}"
                    ;;
            esac
        done
        case "${cmdline}" in
            *curl*)
                cmdline=$(echo "${cmdline}" | sed -e 's/-u .* /-u ******:****** /g')
                ;;
            *PGPASSWORD=*)
                cmdline=$(echo "${cmdline}" | sed -e 's/PGPASSWORD=".*" /PGPASSWORD="**********" /g')
                ;;
        esac
        debug "Running${cmdline}" > /dev/stderr
    fi
    "$@"
}

# Log a success message to the console and publish it to Hipchat
function success {
    print "[$(hostname)]  SUCC: $*"
    hc_announce "[$(hostname)]  SUCC: $*" "green"
}

# -------------------------------------------------------------------------------------
# Internal methods
# -------------------------------------------------------------------------------------

# Publish a message to Hipchat using the REST API
#
#   $1: string: message
#   $2: string: color (yellow/green/red/purple/gray/random)
#   $3: integer: notify (0/1)
#
function hc_announce {
    if [ -z "${HIPCHAT_ROOM}" ]; then
        return 0
    fi
    if [ -z "${HIPCHAT_TOKEN}" ]; then
        return 0
    fi

    if [ -z "$1" ]; then
        print "ERROR: HipChat notification message is missing."
        return 1
    fi

    local hc_color="gray"
    if [ -n "$2" ]; then
        hc_color=$2
    fi
    local hc_notify="false"
    if [ "1" = "$3" ]; then
        hc_notify="true"
    fi

    local hc_message=$(echo "$1" | sed -e 's|"|\\\"|g')
    local hipchat_payload="{\"message\":\"${hc_message}\",\"color\":\"${hc_color}\",\"notify\":\"${hc_notify}\"}"
    local hipchat_url="${HIPCHAT_URL}/v2/room/${HIPCHAT_ROOM}/notification?auth_token=${HIPCHAT_TOKEN}"
    ! curl ${CURL_OPTIONS} -X POST -H "Content-Type: application/json" -d "${hipchat_payload}" "${hipchat_url}"
    true
}
eekfonky
  • 819
  • 4
  • 16
  • 33
  • 1
    What is your exact `source` line? Your error suggest you haven't given the script a [second argument](http://stackoverflow.com/questions/29258603/what-do-0-1-2-mean-in-shell-script). – Julien Lopez Dec 15 '16 at 12:09
  • `SCRIPT_DIR=$(dirname "$0")` `source "${SCRIPT_DIR}/utils.sh"` `source "${SCRIPT_DIR}/common.sh"` – eekfonky Dec 15 '16 at 12:20
  • @eekfonky: do you just need to avoid getting the error (or) need to debug why some variable which is supposed to have a value is actually empty? – Inian Dec 15 '16 at 12:37

2 Answers2

2

$2 is the second positional parameter, or argument if you will, of the script. You need to specify this parameter when "sourcing" a file as you would for any other call to a script that expects a second parameter.

In your case, the script seems to expect three parameters, so call it as:

source /path/to/the/script my_var_name my_err_msg my_bail_msg

For example:

$ cat test.sh
#!/usr/bin/env bash

source test2.sh singing rain
$ cat test2.sh
#!/usr/bin/env bash

var1="$1"
var2="$2"

printf "I'm %s in the %s\n" $var1 $var2
$ ./test.sh
I'm singing in the rain

If you forget to specify the arguments (and if you use set -u) you will get an error:

$ cat test.sh
#!/usr/bin/env bash

set -u

source test2.sh
$ cat test2.sh
#!/usr/bin/env bash

var1="$1"
var2="$2"

printf "I'm %s in the %s\n" $var1 $var2
$ ./test.sh 
test2.sh: line 3: $1: unbound variable
Julien Lopez
  • 1,794
  • 5
  • 18
  • 24
  • Remember, removing `nounset` this just by-passes the error OP is seeing. Not sure if he wants that or debug why the script is referring to a uninitialised variable. – Inian Dec 15 '16 at 12:52
  • @Inian Agreed. I am absolutely **not** advising to remove `set -o nounset` (equivalent to `set -u`). On the contrary, this should always be set. – Julien Lopez Dec 15 '16 at 13:03
1

It seems if I use exec rather than source it works?

eekfonky
  • 819
  • 4
  • 16
  • 33
  • `exec` will not use the context of your script to execute the called one, so if you used `set -u` it will not be applied to the script you are calling. Therefore, if you do `exec script.sh`, the uninitialized `$2` will be set to blank, or `""`, instead of triggering an error. If it works, good for you, but the script you are using clearly is meant to be used with parameters so it might bite you later. ;-) – Julien Lopez Dec 15 '16 at 13:52
  • Thanks, it works just now but I do need a permanent solution so as that I can call it with `source` – eekfonky Dec 15 '16 at 14:23