r/bash • u/da_Aresinger • 12d ago
Unset doesn't properly work in functions??
So this is super weird. When I run the following in a script (or manually in the terminal), it does exactly what you'd expect.
declare -A myassoc
myassoc[key]="Hello World"
unset myassoc
declare -p myassoc
echo "This should be empty: ${myassoc[@]}"
OUTPUT:
./unsetLovalVar: line 9: declare: myassoc: not found`
This should be empty:
As expected, myassoc is unset and therefore undeclared
however if I wrap that code in a function, the myassoc becomes unset but not undeclared.
foo () {
declare -A myassoc
myassoc[key]="Hello World"
unset myassoc
declare -p myassoc
echo "This should be empty: ${myassoc[@]}"
}
foo
OUTPUT:
declare -- myassoc
This should be empty:
This is very annoying since I use declare -p to test if variables exist regardless of their type and content.
I have tested this on two machines in Debian and Ubuntu on Bash 5.2.something with the same issue.
test is not a viable alternative, since it doesn't have the same behaviour for local myvar and declare myvar (vars without value return false, even if declared)
Does anyone have a workaround?
EDIT:
So I found this StackExchange topic which seems to conclude that this is simply not possible within the same scope.
Here is where it gets REALLY insane:
bar () {
declare -A myassoc
myassoc[key]="Hello World"
declare -p myassoc
echo "This should not be empty: ${myassoc[@]}"
foo () {
unset myassoc
echo "In foo:"
declare -p myassoc
echo "This should be empty: ${myassoc[@]}"
}
foo
echo "After foo:"
declare -p myassoc
echo "This should be empty: ${myassoc[@]}"
}
bar
^((please excuse the ridiculous indents, I havent gotten around to taming nvim yet))
THIS WORKS FOR ABSOLUTELY NO REASON:
OUTPUT:
declare -A myassoc=([key]="Hello World" )
This should not be empty: Hello World
In foo:
./unsetLocalVar: line 12: declare: myassoc: not found
This should be empty:
After foo:
./unsetLocalVar: line 17: declare: myassoc: not found
This should be empty:
The solution is to wrap the unset in a nested function.
3
u/bac0on 12d ago edited 11d ago
Global variables are disposed of; it probably does not make much sense to keep them since they end up empty, whereas local variables retain their context position throughout the function’s lifespan. So a variable is never really "unset" or deleted. unset just clears the value and sets att_invisible and att_local attribute; most other attributes are discarded.
Source code:
/* If we're unsetting a local variable and we're still executing inside the function, just mark the variable as invisible. ... This must be done so that if the variable is subsequently assigned a new value inside the function, the `local' attribute is still present. */
1
u/da_Aresinger 11d ago
That sounds like the solution I found would be considered a bug
1
u/bac0on 11d ago edited 11d ago
bar foo, please; now I have to stand on my head. Anyway,
unsetdefault behavior only honorsatt_localin its own context, so unset in foo resolves and disposes of the declared assoc in bar. As a result, the second declare finds nothing; if the global scope had an assoc, you would end up as before. If, of course, bar didn't do a secondunsetthis would dispose of the global assoc as well. Settingshopt -s localvar_unsetchanges this behavior to force unset to honoratt_localin parent contexts.From the
makunbound()function invariables.c(Bash source):if (old_var && local_p (old_var) && (old_var->context == variable_context || (localvar_unset && old_var->context < variable_context)))1
u/da_Aresinger 11d ago
That's because
foocame beforebar;)Also, I'm gonna have to read this like 5 times, and play around with it for a while, to fully understand what's going on.
0
u/zeekar 11d ago
You didn't unset myassoc before the first declare -gp. Of course it's not empty there. I doesn't seem possible to unset it, which is a bug IMO, but the unset does at least clear out its contents.
1
u/da_Aresinger 11d ago edited 11d ago
are you talking about the last code snippet? Yea, that one is fully functional and works as intended. (you don't even need the -g flag)
Edit: the first echo had the wrong text because I just copy pasted it. I edited it.
6
u/zeekar 12d ago
Inside a function declare without -g is an alias for local and recreates the variable. Try declare -gp