20

Say I have a script doing:

some-command "$var1" "$var2" ...

And, in the event that var1 is empty, I'd rather that it be replaced with nothing instead of the empty string, so that the command executed is:

some-command "$var2" ...

and not:

some-command '' "$var2" ...

Is there a simpler way than testing the variable and conditionally including it?

if [ -n "$1" ]; then
    some-command "$var1" "$var2" ...
    # or some variant using arrays to build the command
    # args+=("$var1")
else
    some-command "$var2" ...
fi

Is there a parameter substitution than can expand to nothing in bash, zsh, or the like? I might still want to use globbing in the rest of the arguments, so disabling that and unquoting the variable is not an option.

  • I knew I had seen and probably used this before, but it proved difficult to search. Now that Michael showed the syntax, I remembered where I'd first seen it quickly enough: unix.stackexchange.com/a/269549/70524, unix.stackexchange.com/q/68484/70524 – muru Jan 10 '18 at 7:25
  • If you know it is some kind of parameter substitution, why didn't you look into the parameter expansion section of the man page? (-; – Philippos Jan 10 '18 at 8:29
  • 1
    @Philippos I didn't know what it was at the time, only that I'd seen or used it before. Known knowns and unknown knowns. :( – muru Jan 10 '18 at 8:33
  • 1
    extra cookie points for mentioning using an array to hold the arguments in the question itself. – ilkkachu Jan 10 '18 at 15:12
28

Posix compliant shells and Bash have ${parameter:+word}:

If parameter is unset or null, null shall be substituted; otherwise, the expansion of word (or an empty string if word is omitted) shall be substituted.

So you can just do:

${var1:+"$var1"}

and have var1 be checked, and "$var1" be used if it's set and non-empty (with the ordinary double-quoting rules). Otherwise it expands to nothing. Note that only the inner part is quoted here, not the whole thing.

The same also works in zsh. You have to repeat the variable, so it's not ideal, but it works out exactly as you wanted.

If you want a set-but-empty variable to expand to an empty argument, use ${var1+"$var1"} instead.

  • 1
    Good enough for me. So, in this, the whole thing is not quoted, only the word part is. – muru Jan 10 '18 at 6:57
  • 1
    As the question asked for "bash, zsh, or the like" (and for the Q&A archive), I edited to reflect that this is a posix feature. Even, if you add a /bash tag to the question after my comment. (-; – Philippos Jan 10 '18 at 8:25
  • 2
    I took the liberty of editing in the difference between :+ and +. – ilkkachu Jan 10 '18 at 15:16
  • Thanks much for a POSIX-compliant solution! I try to use sh/dash for potential chokepoint scripts, so I always appreciate it when someone shows how a thing is possible without resorting to Bash. – JamesTheAwesomeDude May 29 '18 at 20:19
  • I was looking for the opposite effect (Expand to something else if it starts as null). It turns out this logic can be inverted by changing the + to a - as in: ${empty_var:-replacement} – Alex Johnson Mar 22 at 6:40
5

That's what zsh does by default when you omit the quotes:

some-command $var1 $var2

Actually, the only reason why you still need quotes in zsh around parameter expansion is to avoid that behaviour (the empty removal) as zsh doesn't have the other problems that affect other shells when you don't quote parameter expansions (the implicit split+glob).

You can do the same with other POSIX-like shells if you disable split and glob:

(IFS=; set -o noglob; some-command $var1 $var2)

Now, I'd argue that if your variable can have either 0 or 1 value, it should be an array and not a scalar variable and use:

some-command "${var1[@]}" "${var2[@]}"

And use var1=(value) when var1 is to contain one value, var1=('') when it's to contain one empty value, and var1=() when it's to contain no value.

0

I ran into this using rsync in a bash script that started the command with or without a -n to toggle dry runs. It turns out that rsync and a number of gnu commands take '' as a valid first argument and act differently than if it were not there.

This took quite awhile to debug as null parameters are almost completely invisible.

Someone on the rsync list showed me a way to avoid this problem while also greatly simplifying my coding. If I understand it correctly, this is a variation on @Stéphane Chazelas's last suggestion.

Build your command arguments in a number of separate variables. These can be set in any order or logic that suits the problem.

Then, at the end, use the variables to construct an array with everything in its proper place and use that as arguments to the actual command.

This way the command is only issued at one place in the code instead of being repeated for each variation of the arguments.

Any variable that is empty just disappears using this method.

I know using eval is highly frowned upon. I don't remember all the details, but I seemed to need it to get things to work this way - something to do with handling parameters with embedded white space.

Example:

dry_run=''
if [[ it is a test run ]]
then
  dry_run='-n'
fi
...
rsync_options=(
  ${dry_run}
  -avushi
  ${delete}
  ${excludes}
  --stats
  --progress
)
...
eval rsync "${rsync_options[@]}" ...
  • That's a worse way of doing what I described in the question (building an array of arguments conditionally). By leaving the variables unquoted, who knows what problems you're leaving yourself open to. – muru Jan 13 '18 at 15:35
  • @muru You're right, but I still need something like this. I don't quite see how to fix it using the techniques from the other answers. It would be great to see a code fragment done the right way that puts it all together. I'm going to play with Stephane's last option later as that might do what I want. – Joe Jan 14 '18 at 10:20
  • Like paste.ubuntu.com/26383847? – muru Jan 14 '18 at 10:31
  • Just got back to this. Thanks for the example. It makes sense. I'm going to work with it. – Joe Mar 9 '18 at 5:11
  • I have a similar use case here, but you could do something like this: if [ "$dry" == "true" ]; then dry="-n"; fi...so if variable dry is true, you make it be -n, and then you build your rsync command as usual and have it like this: rsync ${dry1:+"$dry1"}... if it's null nothing will happen, otherwise it will become -n in your command – Freedo Apr 22 at 2:57

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.