Discussion:
[Help-bash] Distinguish between unset and empty variables in loop.
Christof Warlich
2016-11-23 16:59:36 UTC
Permalink
Hi,
admittedly, the subject sounds a bit strange, but the issue will soon
become clear:
I want to set default values to a list of variables if they are unset,
but leave them untouched otherwise. I want to do this in a loop, because
the list of variables is rather big. But so far, I cannot distinguish
between empty and unset variables. Here is a stripped-down example what
I have:
xxx=hi:
yyy="";
for i in xxx yyy zzz; do
[ -z ${!i} ] && eval "$i=default"; echo $i=${!i};
done
Running this yields:
xxx=hi
yyy=default
zzz=default
whereas I’d like to get the result below because yyy was previously set
to the empty string:
xxx=hi
yyy=
zzz=default
The usual procedures to distinguish between unset and empty variables
(i.e. [ -z ${var+x} ] or [[ -v var ]]) do not work here, as the loop
variable is always set.
Anyone having a idea how this could be done?
Thanks,
Chris
Greg Wooledge
2016-11-23 17:56:33 UTC
Permalink
Post by Christof Warlich
But so far, I cannot distinguish
between empty and unset variables. Here is a stripped-down example what
You can use the ${parameter+word} expansion for this.

imadev:~$ x=''; unset y
imadev:~$ echo "${x+hello}"
hello
imadev:~$ echo "${y+hello}"

imadev:~$

See also http://mywiki.wooledge.org/BashFAQ/083 (or better still, an
older edition of that page... I'm going to have to find the time to
undo all the recent edits that have befouled it).
Christof Warlich
2016-11-23 19:35:33 UTC
Permalink
Post by Greg Wooledge
You can use the ${parameter+word} expansion for this.
imadev:~$ x=''; unset y
imadev:~$ echo "${x+hello}"
hello
imadev:~$ echo "${y+hello}"
imadev:~$
The usual procedures to distinguish between unset and empty
variables (i.e. [ -z ${var+x} ] or [[ -v var ]]) do not work
here, as the loop variable is always set.
Please revisit my example, taking the for-loop into account:

xxx=hi:
yyy="";
for i in xxx yyy zzz; do
[ -z ${!i} ] && eval "$i=default"; echo $i=${!i};
done

I can't see how to apply the ${var+x} (or ${x+hello}} pattern to this situation.

If I'm really missing something here, could you just modify the loop
so that it prints

xxx=hi
yyy=
zzz=default

instead of

xxx=hi
yyy=default
zzz=default

? Again, thanks for your help,

Chris
Greg Wooledge
2016-11-23 19:44:08 UTC
Permalink
Post by Christof Warlich
Post by Christof Warlich
The usual procedures to distinguish between unset and empty
variables (i.e. [ -z ${var+x} ] or [[ -v var ]]) do not work
here, as the loop variable is always set.
Uh... yeah?
Post by Christof Warlich
yyy="";
for i in xxx yyy zzz; do
[ -z ${!i} ] && eval "$i=default"; echo $i=${!i};
done
Oh. You're doing some bizarre indirection thing. You want an associative
array instead.
Post by Christof Warlich
I can't see how to apply the ${var+x} (or ${x+hello}} pattern to this situation.
declare -A map
map[xxx]=hi
map[yyy]=""
unset 'map[zzz]'

for i in xxx yyy zzz; do
if test "${map[$i]+defined}"; then
echo "element $i is defined in the map"
fi
done

If you can't use an associative array because you're stuck with bash 3,
then my advice is to upgrade to bash 4, or switch to something that HAS
associative arrays, like awk, perl, python, tcl, ....
Christof Warlich
2016-11-23 20:26:03 UTC
Permalink
Oh. You're doing some bizarre indirection thing.
You want an associative array instead.
Well, I can't use an associative array, but not because I'm using an old
bash version, but because my initial point was that I want to set useful
I want to set default values to a list of variables if they are unset,
but leave them untouched otherwise. I want to do this in a loop,
because the list of variables is rather big.
The variables are part of a script's user interface: They may be set
(and, by the way, need to be exported to be visible by the script) by
the script's user. AFAIK, (associative) arrays cannot be exported.
Furthermore, most of the defaults are just fine for the script in 99% of
all use cases, so the user may typically only need to preseed a small
subset of these variables. Thus, even if an array _could_ be used, it
would make the script's user interface overly complicated.

Anyway, the more I think about this, I fear that that this cannot work,
but I'd still be more than happy if someone would come up with some
smart idea :-).

Cheers,

Chris
Christof Warlich
2016-11-24 17:54:37 UTC
Permalink
Just for the record, here is a solution of the problem:

xxx=hi;
yyy="";
unset zzz;
for i in xxx yyy zzz; do
(set -o posix; set | grep -q "^$i=") || eval "$i=default"; echo $i=${!i};
done

It prints

xxx=hi
yyy=
zzz=default

as desired.

This is clearly a hack, but it just works, so who cares.

Cheers,

Chris
Dmitry Alexandrov
2016-12-15 02:36:04 UTC
Permalink
Post by Christof Warlich
xxx=hi;
yyy="";
unset zzz;
for i in xxx yyy zzz; do
(set -o posix; set | grep -q "^$i=") || eval "$i=default"; echo $i=${!i};
done
It prints
xxx=hi
yyy=
zzz=default
as desired.
This is clearly a hack, but it just works, so who cares.
OMG.

,----[ (info "(bash) Bash Conditional Expressions") ]
| `-v VARNAME'
| True if the shell variable VARNAME is set (has been assigned a
| value).
`----

,----[ ./warlich ]
| #!/bin/bash
|
| xxx=hi
| yyy=""
| unset zzz
|
| for v in xxx yyy zzz; do
| [[ -v $v ]] || printf -v "$v" 'default'
| printf '%s=%s\n' "$v" "${!v}"
| done
`----

,----
| $ ./warlich
| xxx=hi
| yyy=
| zzz=default
`----
Christof Warlich
2016-12-15 19:47:11 UTC
Permalink
Post by Dmitry Alexandrov
,----[ ./warlich ]
| #!/bin/bash
|
| xxx=hi
| yyy=""
| unset zzz
|
| for v in xxx yyy zzz; do
| [[ -v $v ]] || printf -v "$v" 'default'
| printf '%s=%s\n' "$v" "${!v}"
| done
`----
,----
| $ ./warlich
| xxx=hi
| yyy=
| zzz=default
`----
That's definitly much better than my hack, thanks :-).

I would just not have expected that the "unset-ness" of a variable is
still available directly through the loops index variable ...
Chet Ramey
2016-12-15 19:53:56 UTC
Permalink
Post by Christof Warlich
Post by Dmitry Alexandrov
,----[ ./warlich ]
| #!/bin/bash
|
| xxx=hi
| yyy=""
| unset zzz
|
| for v in xxx yyy zzz; do
| [[ -v $v ]] || printf -v "$v" 'default'
| printf '%s=%s\n' "$v" "${!v}"
| done
`----
,----
| $ ./warlich
| xxx=hi
| yyy=
| zzz=default
`----
That's definitly much better than my hack, thanks :-).
I would just not have expected that the "unset-ness" of a variable is still
available directly through the loops index variable ...
The -v operator takes a variable name; $v expands to the desired variable
name.
--
``The lyf so short, the craft so long to lerne.'' - Chaucer
``Ars longa, vita brevis'' - Hippocrates
Chet Ramey, UTech, CWRU ***@case.edu http://cnswww.cns.cwru.edu/~chet/
Loading...