Search code examples
bashechogetopts

getops $OPTARG is empty if flag value contains brackets


When I pass a flag containing [...] to my bash script, getops gives me an empty string when I try to grab the value with $OPTARG.

shopt -s nullglob
while getopts ":f:" opt; do
  case $opt in
   f)
      str=$OPTARG
      ;;
  esac
done
echo ${str}

Running the script:

$ script.sh -f [0.0.0.0]
<blank line>

How can I get the original value back inside the script?


Solution

  • Short summary: Double-quote your variable references. And use shellcheck.net.

    Long explanation: When you use a variable without double-quotes around it (e.g. echo ${str}), the shell tries to split its value into words, and expand anything that looks like a wildcard expression into a list of matching files. In the case of [0.0.0.0], the brackets make it a wildcard expression that'll match either the character "0" or "." (equivalent to [0.]). If you had a file named "0", it would expand to that string. With no matching file(s), it's normally left unexpanded, but with the nullglob set it expands to ... null.

    Turning off nullglob solves the problem if there are no matching files, but isn't really the right way do it. I remember (but can't find right now) a question we had about a script that failed on one particular computer, and it turned out the reason was that one computer happened to have a file that matched a bracket expression in an unquoted variable's value.

    The right solution is to put double-quotes around the variable reference. This tells the shell to skip word splitting and wildcard expansion. Here's an interactive example:

    $ str='[0.0.0.0]'    # Quotes aren't actually needed here, but they don't hurt
    $ echo $str    # This works without nullglob or a matching file
    [0.0.0.0]
    $ shopt -s nullglob
    $ echo $str    # This fails because of nullglob
    
    $ shopt -u nullglob
    $ touch 0
    $ echo $str    # This fails because of a matching file
    0
    $ echo "$str"    # This just works, no matter whether file(s) match and/or nullglob is set
    [0.0.0.0]
    

    So in your script, simply change the last line to:

    echo "${str}"
    

    Note that double-quotes are not required in either case $opt in or str=$OPTARG because variables in those specific contexts aren't subject to word splitting or wildcard expansion. But IMO keeping track of which contexts it's safe to leave the double-quotes off is more hassle than it's worth, and you should just double-quote 'em all.

    BTW, shellcheck.net is good at spotting common mistakes like this; I recommend feeding your scripts through it, since this is probably not the only place you have this problem.