Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unset readonly variable in bash

Tags:

bash

unset

How do I unset a readonly variable in Bash?

$ readonly PI=3.14

$ unset PI
bash: PI: readonly variable

or is it not possible?

like image 238
Kokizzu Avatar asked Jul 01 '13 03:07

Kokizzu


People also ask

How do I get rid of readonly variables?

An alternative if gdb is unavailable: You can use the enable command to load a custom builtin that will let you unset the read-only attribute. The gist of the code that does it: SETVARATTR (find_variable ("TMOUT"), att_readonly, 1); Obviously, you'd replace TMOUT with the variable you care about.

How do I remove a read only variable in Linux?

You can't delete mySite. The whole point of the readonly command is to make it final and permanent (until the shell process terminates). If you need to change a variable, don't mark it readonly.

How do you unset a variable in shell script?

Unsetting or deleting a variable directs the shell to remove the variable from the list of variables that it tracks. Once you unset a variable, you cannot access the stored value in the variable. The above example does not print anything. You cannot use the unset command to unset variables that are marked readonly.


4 Answers

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.

like image 79
anishsane Avatar answered Oct 16 '22 20:10

anishsane


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
like image 35
vip9937 Avatar answered Oct 16 '22 22:10

vip9937


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 of gdb if readonly flag not present
  • lines 9 to 12 while read ... result= ... done get return code of call (int) unbind_variable() in gdb output
  • line 13 gdb syntax with use of --pid and --ex (see gdb --help).
  • line 15 return $result of unbind_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
    
like image 44
F. Hauri Avatar answered Oct 16 '22 20:10

F. Hauri


In zsh,

% typeset +r PI
% unset PI

(Yes, I know the question says bash. But when you Google for zsh, you also get a bunch of bash questions.)

like image 40
Radon Rosborough Avatar answered Oct 16 '22 22:10

Radon Rosborough