74

I am trying to learn how to use getopts so that I can have scripts with parsed input (although I think getopts could be better). I am trying to just write a simple script to return partition usage percentages. The problem is that one of my bash functions does not seem to like that I reference $1 as an variable within the function. The reason I reference $1 is because the get_percent function can be passed a mount point as an optional argument to display instead of all of the mount points.

The script

#!/usr/bin/bash

set -e
set -u
set -o pipefail

get_percent(){
    if [ -n "$1" ] 
    then
        df -h $1 | tail -n +2 | awk '{ print $1,"\t",$5 }'
    else
        df -h | tail -n +2 | awk '{ print $1,"\t",$5 }'
    fi
}

usage(){
    echo "script usage: $(basename $0) [-h] [-p] [-m mount_point]" >&2
}

# If the user doesn't supply any arguments, we run the script as normal
if [ $# -eq 0 ];
then
    get_percent
    exit 0
fi
# ...

The Output

$ bash thing.sh
thing.sh: line 8: $1: unbound variable

$ bash -x thing.sh
+ set -e
+ set -u
+ set -o pipefail
+ '[' 0 -eq 0 ']'
+ get_percent
thing.sh: line 8: $1: unbound variable
3
  • I don't think this has anything to do with getopts, does it? Your script exits due to -u before calling getopts.
    – ilkkachu
    Aug 16, 2018 at 18:31
  • @ikkachu no I guess it doesn't. But I'm not sure I can change the title now. Aug 16, 2018 at 18:33
  • There should be that small "edit" text under the post, just beneath the tags in a question
    – ilkkachu
    Aug 16, 2018 at 18:36

5 Answers 5

94

set -u will abort exactly as you describe if you reference a variable which has not been set. You are invoking your script with no arguments, so get_percent is being invoked with no arguments, causing $1 to be unset.

Either check for this before invoking your function, or use default expansions (${1-default} will expand to default if not already set to something else).

5
  • I suspected this, but I couldn't think of a way around it. Default expansion seems to have fixed it. Thank you very much! Aug 16, 2018 at 18:10
  • 20
    In particular, one could use [ -n "${1-}" ] (that is, with an empty default value) to see if the parameter is set and non-empty; or [ "${1+x}" = x ] to see if it's set, even if empty.
    – ilkkachu
    Aug 16, 2018 at 18:30
  • I still get unbound variable error despite using if [[ -n ${1-default} ]] Dec 7, 2019 at 2:59
  • 4
    @ChaitanyaBapat I was still getting unbound variable as well until I used :- instead of -. So, for me at least, the ${1:-default} no loner raised the error. Apr 9, 2020 at 11:26
  • As a side note, if you are trying to access an array element with an index out of range, you get the same error (with syntax ${my_array[$index]}). Dec 3, 2021 at 23:16
16

This is the effect of set -u.

You could check $# inside the function and avoid referencing $1 if it is not set.

With $# you can access the number of parameters. In global context it is the number of parameters to the script, in a function it is the number of parameters to the function.

In the context of the question, it is

if [ $# -ge 1 ] && [ -n "$1" ]
then
    df -h $1 | tail -n +2 | awk '{ print $1,"\t",$5 }'
else
    df -h | tail -n +2 | awk '{ print $1,"\t",$5 }'
fi

Note that you have to use [ $# -ge 1 ] && [ -n "$1" ] and not [ $# -ge 1 -a -n "$1" ], because that would first evaluate $1 and then check $#.

0
7

With "set -u" in the script any reference to an unset variable will cause an error, especially testing if it has a value, which is what the script is doing.

The following function will test for unset variable without tripping the set -u:

if:IsSet() {
  [[ ${!1-x} == x ]] && return 1 || return 0
}

Using it:

if:IsSet someVariableName || echo "someVariableName is not set"

As for the actual function, test whether an argument was passed in, not whether it is empty. This will avoid the unset variable issue without you taking any special steps, like testing whether the variable has a value.

get_percent(){
    if [ ${#1} -eq 0 ] 

BTW: Colon in a function name is just a character. It is valid in a function name in Bash, as are %, }, {, [, and a bunch of others :-)

1
  • This does not work if someVariableName happens to be set to 'x'. You could fix it by using [[ ${!1-x} == x ]] && [[ ${!1-y} == y ]] && return 1; return 0 in your if:IsSet function. Jan 6 at 14:49
6

Since this is bash you can sidestep the check for $1 being set and just use "$@" ($1 is the first parameter, $@ is all of them; when double-quoted, it disappears completely if it has no values, which avoids it being caught by set -u):

get_percent() {
    df -h "$@" | awk 'NR>1 { printf "%s\t%s\n", $1, $5 }'
}

I've also tweaked the rest of the line slightly so that you don't get {space}{tab}{space} between the two values you output but insead you get just a {tab}. If you really want the two invisible spaces then change the awk to use printf "%s \t %s\n", $1, $5.

2
  • I will have to look into this. I'm not familiar with that variable type. Thanks Aug 16, 2018 at 18:29
  • @TimothyPulliam I've added a short explanation of $@ for you
    – roaima
    Dec 7, 2019 at 9:34
3

Other answers have mentioned that this is the effect of set -u. None of the other answers have mentioned that set -u can be reversed with set +u:

$ echo $VAR

$ set -u
$ echo $VAR
bash: VAR: unbound variable
$ set +u
$ echo $VAR

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.