7

I'm running this kind of code:

#!/usr/bin/env bash
set -u
exclude1='--exclude=/path/*'
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
RESULT=("${PIPESTATUS[@]}")
... 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
RESULT=("${PIPESTATUS[@]}")
... etc ...

Anyone have a smarter idea?

3 Answers 3

7
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

exclude3=''
3
  • 1
    Nice trick, but if $exclude3 is unset then Bash will issue an error because 'set -u' is used as a safety measure. Apr 28, 2013 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, 2013 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, 2013 at 0:38
7

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

excludes=()
excludes+=('--exclude=/path/*')
…
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
1

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

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .