Unset readonly variable in bash
Actually, you can unset a readonly variable. but I must warn that this is a hacky method. Adding this answer, only as information, not as a recommendation. Use it at your own risk. Tested on ubuntu 13.04, bash 4.2.45.
This method involves knowing a bit of bash source code & it's inherited from this answer.
$ readonly PI=3.14
$ unset PI
-bash: unset: PI: cannot unset: readonly variable
$ cat << EOF| sudo gdb
attach $$
call unbind_variable("PI")
detach
EOF
$ echo $PI
$
A oneliner answer is to use the batch mode and other commandline flags, as provided in F. Hauri's answer:
$ sudo gdb -ex 'call unbind_variable("PI")' --pid=$$ --batch
sudo
may or may not be needed based on your kernel's ptrace_scope settings. Check the comments on vip9937's answer for more details.
I tried the gdb hack above because I want to unset TMOUT (to disable auto-logout), but on the machine that has TMOUT set as read only, I'm not allowed to use sudo. But since I own the bash process, I don't need sudo. However, the syntax didn't quite work with the machine I'm on.
This did work, though (I put it in my .bashrc file):
# Disable the stupid auto-logout
unset TMOUT > /dev/null 2>&1
if [ $? -ne 0 ]; then
gdb <<EOF > /dev/null 2>&1
attach $$
call unbind_variable("TMOUT")
detach
quit
EOF
fi
Shortly: inspired by anishsane's answer
Edit 2021-11-10: Add (int)
to cast unbind_variable
result.
But with simplier syntax:
$ gdb -ex 'call (int) unbind_variable("PI")' --pid=$$ --batch
With some improvement, as a function:
My destroy
function:
Or How to play with variable meta data. Note usage of rare bashisms: local -n VARIABLE=$1
and ${VARIABLE@a}
...
destroy () {
declare -p $1 &>/dev/null || return -1 # Return if variable not exist
local -n variable=$1
local reslne result flags=${variable@a}
[ -z "$flags" ] || [ "${flags//*r*}" ] && {
unset $1 # Don't run gdb if variable is not readonly.
return $?
}
while read -r resline; do
[ "$resline" ] && [ -z "${resline%%\$1 = *}" ] &&
result=${resline##*1 = }
done < <(
exec gdb 2>&1 -ex 'call (int) unbind_variable("'$1'")' --pid=$$ --batch
)
return $result
}
You could copy this to a bash source file called destroy.bash
, for sample...
Explanation:
1 destroy () { 2 local -n variable=$1 3 declare -p $1 &>/dev/null || return -1 # Return if variable not exist 4 local reslne result flags=${variable@a} 5 [ -z "$flags" ] || [ "${flags//*r*}" ] && { 6 unset $1 # Don't run gdb if variable is not readonly. 7 return $? 8 } 9 while read resline; do 10 [ "$resline" ] && [ -z "${resline%\$1 = *}" ] && 11 result=${resline##*1 = } 12 done < <( 13 gdb 2>&1 -ex 'call (int) unbind_variable("'$1'")' --pid=$$ --batch 14 ) 15 return $result 16 }
- line 2 create a local reference to submited variable.
- line 3 prevent running on non existant variable
- line 4 store parameter's attributes (meta) into
$flags
. - lines 5 to 8 will run
unset
instead ofgdb
if readonly flag not present - lines 9 to 12
while read ... result= ... done
get return code ofcall (int) unbind_variable()
ingdb
output - line 13
gdb
syntax with use of--pid
and--ex
(seegdb --help
). - line 15 return
$result
ofunbind_variable()
command.
In use:
$ . destroy.bash
1st with any regular (read-write) variable:
$ declare PI=$(bc -l <<<'4*a(1)') $ echo $PI 3.14159265358979323844 $ echo ${PI@a} # flags $ declare -p PI declare -- PI="3.14159265358979323844" $ destroy PI $ echo $? 0 $ declare -p PI bash: declare: PI: not found
2nd with read only variable:
$ declare -r PI=$(bc -l <<<'4*a(1)') $ declare -p PI declare -r PI="3.14159265358979323844" $ echo ${PI@a} # flags r $ unset PI bash: unset: PI: cannot unset: readonly variable $ destroy PI $ echo $? 0 $ declare -p PI bash: declare: PI: not found
3rd with non existant variable:
$ destroy PI $ echo $? 255