I'm running this kind of code:

#!/usr/bin/env bash
set -u
exclude2='--exclude=/path with spaces/*'
exclude3=''        # any 'exclude' can be empty
tar -czf backup.tgz "$exclude1" "$exclude2" "$exclude3" 2>&1 | grep -i 'my_reg_exp' > error.log
... etc ...

When I run this code, I get this error:

tar: : Cannot stat: No such file or directory

This is because "$exclude3" is translated as an empty argument. Exactly as if I did this:

tar -czf backup.tgz "$exclude1" "$exclude2" ''

One way to avoid this error is to remove the double quotes around $excludeX. But this is a problem if $excludeX contains any space or other strange characters.

Another way would be to use eval but because I need to keep the double quotes, I don't see how to suppress the quotes AND the empty arguments when needed.

The only solution I found is to construct the command-line with string concatenation:

CMD='tar -czf backup.tgz'
if [[ -n "$exclude1" ]]; then CMD+=" \"$exclude1\" "; fi
if [[ -n "$exclude2" ]]; then CMD+=" \"$exclude2\" "; fi
if [[ -n "$exclude3" ]]; then CMD+=" \"$exclude3\" "; fi
eval $CMD 2>&1 | grep -i 'my_reg_exp' > error.log
... etc ...

Anyone have a smarter idea?

tar -czf backup.tgz "$exclude1" "$exclude2" ${exclude3+"$exclude3"} 2>&1

${exclude3+"$exclude3"} expands to nothing, if $exclude3 is unset, and to "$exclude3", if it is set.

(and similarly for the other variables that are potentially unset.)

Note that there is a difference between an unset variable and a variable that is set to the empty string, so you should use

unset exclude3

instead of

  • 1
    Nice trick, but if $exclude3 is unset then Bash will issue an error because 'set -u' is used as a safety measure. – Gregory MOUSSAT Apr 28 '13 at 0:11
  • It works as long as you only test whether the variable is set or not. Testing doesn't count as trying to perform parameter expansion on the variable. – Uwe Apr 28 '13 at 0:21
  • 1
    By the way, there is a related construction that treats unset and empty variables in the same way, namely ${exclude3:+"$exclude3"} (note the colon). – Uwe Apr 28 '13 at 0:38

Since you're working in bash, use an array.

tar -czf backup.tgz "${excludes[@]}"

If you have an optional entry in some variable, add it in a conditional.

if [[ -n $exclude_or_empty ]]; then excludes+=("$exclude_or_empty"); fi

This works for me in bash on Ubuntu, but I have no clue, whether it's POSIX compliant/compatible:

tar -czf backup.tgz \
$(if "$exclude1" != ""); then echo "$exclude1"; fi
$(if "$exclude2" != ""); then echo "$exclude3"; fi
$(if "$exclude3" != ""); then echo "$exclude3"; fi

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.