Search code examples
bashglobshopt

Is there an easy way to set nullglob for one glob


In bash, if you do this:

mkdir /tmp/empty
array=(/tmp/empty/*)

you find that array now has one element, "/tmp/empty/*", not zero as you'd like. Thankfully, this can be avoided by turning on the nullglob shell option using shopt -s nullglob

But nullglob is global, and when editing an existing shell script, may break things (e.g., did someone check the exit code of ls foo* to check if there are files named starting with "foo"?). So, ideally, I'd like to turn it on only for a small scope—ideally, one filename expansion. You can turn it off again using shopt -u nullglob But of course only if it was disabled before:

old_nullglob=$(shopt -p | grep 'nullglob$')
shopt -s nullglob
array=(/tmp/empty/*)
eval "$old_nullglob"
unset -v old_nullglob

makes me think there must be a better way. The obvious "put it in a subshell" doesn't work as of course the variable assignment dies with the subshell. Other than waiting for the Austin group to import ksh93 syntax, is there?


Solution

  • With mapfile in Bash 4, you can load an array from a subshell with something like: mapfile array < <(shopt -s nullglob; for f in ./*; do echo "$f"; done). Full example:

    $ shopt nullglob
    nullglob        off
    $ find
    .
    ./bar baz
    ./qux quux
    $ mapfile array < <(shopt -s nullglob; for f in ./*; do echo "$f"; done)
    $ shopt nullglob
    nullglob        off
    $ echo ${#array[@]}
    2
    $ echo ${array[0]}
    bar baz
    $ echo ${array[1]}
    qux quux
    $ rm *
    $ mapfile array < <(shopt -s nullglob; for f in ./*; do echo "$f"; done)
    $ echo ${#array[@]}
    0
    
    • Be sure to glob with ./* instead of a bare * when using echo to print the file name
    • Doesn't work with newline characters in the filename :( as pointed out by derobert

    If you need to handle newlines in the filename, you will have to do the much more verbose:

    array=()
    while read -r -d $'\0'; do
        array+=("$REPLY")
    done < <(shopt -s nullglob; for f in ./*; do printf "$f\0"; done)
    

    But by this point, it may be simpler to follow the advice of one of the other answers.