在bash中取消设置readonly变量

时间:2013-07-01 03:30:38

标签: bash unset

如何在Bash中取消设置readonly变量?

$ readonly PI=3.14

$ unset PI
bash: PI: readonly variable

还是不可能?

15 个答案:

答案 0 :(得分:86)

实际上,您可以取消设置只读变量。但我必须警告,这是一种黑客方法。添加此答案,仅作为信息,而不是建议。使用风险由您自己承担。在ubuntu 13.04上测试,bash 4.2.45。

这种方法涉及了解一些bash源代码&它继承自this回答。

$ 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

$

答案 1 :(得分:42)

我尝试了上面的gdb hack,因为我想取消设置TMOUT(禁用自动注销),但是在TMOUT设置为只读的机器上,我不允许使用sudo。但是因为我拥有bash过程,所以我不需要sudo。但是,语法对我正在使用的机器不起作用。

这确实有效(我把它放在我的.bashrc文件中):

# 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

答案 2 :(得分:5)

根据手册页:

   unset [-fv] [name ...]
          ...   Read-only  variables  may  not  be
          unset. ...

如果您还没有导出变量,可以使用exec "$0" "$@"重新启动shell,当然您也会丢失所有其他未导出的变量。看来如果你开始一个没有exec的新shell,它就会失去该shell的只读属性。

答案 3 :(得分:3)

简而言之:受anishsane's answer启发

但是使用更简单的语法:

position: relative;

作为一项功能进行了一些改进:

我的gdb -ex 'call unbind_variable("PI")' --pid=$$ --batch 函数:

如何检查可变元数据 ...

destroy

您可以将其复制到名为destroy () { local -n variable=$1 declare -p $1 &>/dev/null || return -1 # Return if variable not exist local reslne result flags=${variable@a} [ -z "$flags" ] || [ "${flags//*r*}" ] && { unset $1 # Don't run gdb if variable is not readonly. return $? } while read resline; do [ "$resline" ] && [ -z "${resline%\$1 = *}" ] && result=${resline##*1 = } done < <( gdb 2>&1 -ex 'call unbind_variable("'$1'")' --pid=$$ --batch ) return $result } bash源文件中,以获取示例...

说明:

destroy.bash
  • 第2行为要用于元数据的提交变量创建引用
  • 第3行阻止在不存在的变量上运行
  • 第4行将参数的属性存储到 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 unbind_variable("'$1'")' --pid=$$ --batch 14 ) 15 return $result 16 } 变量中。
  • 如果没有只读标志,则
  • 第5至8行将运行$flags而不是unset
  • 第9至12行gdbwhile read ... result= ... done输出中获取call unbind的返回代码
  • 第13行gdb语法,使用gdb--pid(请参阅--ex)。
  • 第15行返回gdb --help命令的$result

使用中:

call unbind

答案 4 :(得分:2)

readonly命令使其成为final和permanent,直到shell进程终止。如果您需要更改变量,请不要将其标记为只读。

答案 5 :(得分:2)

不,不在当前的shell中。如果您希望为其分配一个新值,则必须在新的shell中分叉,它将具有新的含义,不会被视为read only

$ { ( readonly pi=3.14; echo $pi ); pi=400; echo $pi; unset pi; echo [$pi]; }
3.14
400
[]

答案 6 :(得分:2)

在zsh,

$ typeset +r PI

(是的,我知道问题是bash。但是当你使用Google for zsh时,你也会遇到一堆bash问题。)

答案 7 :(得分:2)

使用GDB非常慢。请尝试使用ctypes.sh。它的工作原理是使用libffi直接调用bash的unbind_variable(),这与使用任何其他bash内置的速度一样快:

$ readonly PI=3.14
$ unset PI
bash: unset: PI: cannot unset: readonly variable

$ source ctypes.sh
$ dlcall unbind_variable string:PI

$ declare -p PI
bash: declare: PI: not found

首先,您需要安装ctypes.sh:

$ git clone https://github.com/taviso/ctypes.sh.git
$ cd ctypes.sh
$ ./autogen.sh
$ ./configure
$ make
$ sudo make install

有关完整说明和文档,请参阅https://github.com/taviso/ctypes.sh

对于好奇,是的,这可以让你调用bash中的任何函数,或链接到bash的任何库中的任何函数,或者如果你愿意,甚至可以调用任何外部动态加载库。 Bash现在和perl一样危险......; - )

答案 8 :(得分:1)

您不能,从unset的手册页

  

对于每个名称,删除相应的变量或函数。如果未提供任何选项,或者给出了-v选项,则为每个名称                 是指shell变量。 可能无法取消设置只读变量。如果指定了-f,则每个名称都引用一个shell函数,并且                 功能定义已删除。从传递给后续命令的环境中删除每个未设置的变量或函数。如果                 RANDOM,SECONDS,LINENO,HISTCMD,FUNCNAME,GROUPS或DIRSTACK中的任何一个都未设置,即使它们失去了特殊属性                 随后重置。除非名称是只读的,否则退出状态为true。

答案 9 :(得分:1)

特别是对于TMOUT变量。如果gdb不可用,另一个选择是将bash复制到您的主目录,并将二进制文件中的TMOUT字符串修补为其他内容,例如XMOUX。然后运行这个额外的shell层,你就不会超时了。

答案 10 :(得分:1)

另一种解除&#34;未设置的方式&#34; Bash中的只读变量是在一次性上下文中声明该变量是只读的:

foo(){ declare -r PI=3.14; baz; }
bar(){ local PI=3.14; baz; }

baz(){ PI=3.1415927; echo PI=$PI; }

foo;
  

bash:PI:只读变量

bar; 
  

PI = 3.1415927

虽然这不是&#34;未设置&#34;在范围内,这可能是原作者的意图,这肯定是从baz()的角度设置变量只读,然后从baz()的角度来看它是读写的,你只需要先考虑一下你的脚本。

答案 11 :(得分:1)

如果gdb不可用,则可以选择一种替代方法:您可以使用the enable command来加载a custom builtin,这将使您取消设置只读属性。做到这一点的代码要点:

SETVARATTR (find_variable ("TMOUT"), att_readonly, 1);

很显然,您将TMOUT替换为您关心的变量。

如果您不想自己将其转换为内置函数,则我在GitHub上进行了bash编程,并添加了一个名为readwrite的完全编写且可以编译的可加载内置函数。提交在https://github.com/josephcsible/bash/commit/bcec716f4ca958e9c55a976050947d2327bcc195。如果要使用它,请在提交时获得Bash源,运行./configure && make loadables进行构建,然后运行enable -f examples/loadables/readwrite readwrite将其添加到运行的会话中,然后运行readwrite TMOUT来使用它。 / p>

答案 12 :(得分:1)

另一个没有GDBexternal binary(实际上是强调Graham Nicholls注释)的解决方案是使用exec

在我的情况下,/etc/profile.d/xxx中设置了一个烦人的只读变量。

引用bash手册:

  

“当bash作为交互式登录shell调用时,它首先从文件/ etc / profile中读取并执行命令” [...]

     

启动不是登录shell的交互式shell时,bash从/etc/bash.bashrc [...]

读取并执行命令。

解决方法的要点是放入我的~/.bash_profile

if [ -n "$annoying_variable" ]
then exec env annoying_variable='' /bin/bash
# or: then exec env -i /bin/bash
fi

警告::为避免递归(如果您只能通过SSH访问您的帐户,这将使您无法登录),应确保bashrc或bashrc不会自动设置“讨厌的变量”在支票上设置另一个变量,例如:

if [ -n "$annoying_variable" ] && [ "${SHLVL:-1}" = 1 ]
then exec env annoying_variable='' SHLVL=$((SHLVL+1)) ${SHELL:-/bin/bash}
fi

答案 13 :(得分:0)

$ PI=3.17
$ export PI
$ readonly PI
$ echo $PI
3.17
$ PI=3.14
-bash: PI: readonly variable
$ echo $PI
3.17

现在该怎么办?

$ exec $BASH
$ echo $PI
3.17
$ PI=3.14
$ echo $PI
3.14
$

子shell可以继承父级变量,但不会继承其受保护状态。

答案 14 :(得分:0)

$ readonly PI=3.14

$ unset PI
bash: PI: readonly variable

$ gdb --batch-silent --pid=$$ --eval-command='call (int) unbind_variable("PI")'

$ [[ ! -v PI ]] && echo "PI is unset ✔️"
PI is unset ✔️

注意事项:

  1. 使用 bash 5.0.17gdb 10.1 进行测试。
  2. -v varname 中添加了 bash 4.2 测试。如果设置了 shell 变量 True(已分配值),则为“varname”。 – bash reference manual
  3. 注意 int 的演员表。否则,将导致以下错误:'unbind_variable' has unknown return type; cast the call to its declared return typebash source code 表明 unbind_variable 函数的返回类型是 int
  4. 这个答案与an answer over at superuser.com基本相同。我将演员表添加到 int 以克服 unknown return type 错误。