在bash中取消设置只读变量


78

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

$ readonly PI=3.14

$ unset PI
bash: PI: readonly variable

还是不可能?


啊,我的坏tldp.org/LDP/Bash-Beginners-Guide/html/sect_10_01.html将变量设为只读。这些变量然后不能被后续的赋值语句赋值,也不能被取消设置。
Kokizzu

通常,变量是只读的,因为/ etc / profile包含很多这样的行readonly TMOUT。我喜欢注释这些行,并打开与该Linux机器的新连接。
ROMANIA_engineer

1
@ROMANIA_engineer或者,只需执行bash --norc,然后手动设置所需的内容,或在自己的rc文件中设置-例如:source〜/ .gnbashrc
Graham Nicholls

Answers:


106

实际上,您可以取消设置readonly变量。但我必须警告这是一种骇人听闻的方法。添加此答案仅作为参考,不作为建议。需要您自担风险使用它。在Ubuntu 13.04,Bash 4.2.45上测试。

此方法涉及一些bash源代码,并且源于答案。

$ 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

$

一个单一的答案是使用批处理模式和其他命令行标志,如F. Hauri的答案中所提供

$ sudo gdb -ex 'call unbind_variable("PI")' --pid=$$ --batch

sudo根据您内核的ptrace_scope设置,可能需要或不需要。有关更多详细信息,请查看vip9937答案的评论。


50
现在这就是所谓的乡下人bash编程;)
Floyd

5
注意:不要试图更改cat << EOF| sudo gdbsudo gdb << EOF。它可能 没有工作,因为重定向输入提供商-bash正由于停止gdb附件。
2014年

我认为响应稍微准确一些,因为它不需要sudo并且可以正确结束gdb
Rafareino

1
^^在stdin处的EOF和显式退出都会干净地退出gdb。
2015年

2
我喜欢一个班轮:echo -e "attach $$\n call unbind_variable(\"PI\")\n detach" | gdb
萨蒂亚·米什拉

48

我尝试了上面的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

3
我建议使用-q -n选项来使每个安全性gdb均不加载任何.gdbinit文件。
卢卡斯·西蒙

3
“因为我拥有bash进程,所以不需要sudo”。请注意,这取决于您所使用的操作系统以及其配置方式。对于大多数当前使用的Linux内核版本,可以通过/proc/sys/kernel/yama/ptrace_scope进行控制。最常见的值是0,在这种情况下,您可以执行此操作;1在这种情况下,您可能不能这样做,因为gdb它不是bash要调试的进程的直接父级
伊莱亚·卡根

尽管-q并且-n很有帮助,但它们(即-q)不会保持沉默gdb,因此/dev/null仍然需要重定向。不过,这是一个很好的建议,@ LucasCimon
fbicknel

任何想法如何在没有gdb的计算机上执行类似的操作?
lightswitch05

1
@ lightswitch05:请参阅我的ctypes.sh答案
威尔

6

根据手册页:

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

如果尚未导出变量,则可以使用它exec "$0" "$@"来重新启动Shell,当然,您还将丢失所有其他未导出的变量。看来,如果您启动一个不带的新shell exec,它将失去该shell的只读属性。


6

使用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一样危险... ;-)


1
我假设,你说include ctypes.sh你的意思source ctypes.sh. ctypes.sh
暂停,直到另行通知。

5

简短说明:受奥尼沙尼(Anishsane)的回答启发

但是使用更简单的语法:

gdb -ex 'call unbind_variable("PI")' --pid=$$ --batch

经过改进,功能如下:

我的destroy功能:

如何使用可变的元数据。注意罕见bashisms的用法:local -n VARIABLE=$1${VARIABLE@a}...

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,作为示例...

说明:

 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  }
  • 第2行创建本地参考提交变量。
  • 第3行防止在不存在的变量上运行
  • 第4行将参数的属性(元)存储到 $flags
  • 第5至8行将运行,unset而不是gdbif readonly标志不存在
  • 第9至12行while read ... result= ... done获取call unbindin的返回码gdb输出
  • 第13行gdb语法,使用--pidand --ex(请参阅gdb --help)。
  • 第15行返回$resultcall unbind命令。

正在使用:

source 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

# now 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

# and with non existant variable
destroy PI
echo $?
255


3

专门写给TMOUT变量。如果gdb不可用,另一种选择是将bash复制到主目录,并将二进制文件中的TMOUT字符串修补到其他文件,例如XMOUX。然后运行额外的外壳层,您将不会超时。


2
比gdb骇客更邪恶。所以... +1!
阿洛瓦·马哈德


2

不,不在当前shell中。如果您希望为其分配新值,则必须在其中具有新含义且不会被视为的新shell上派生read only

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

2
$ 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
$

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


2

如果gdb不可用,则可以选择一种替代方法:您可以使用enable命令来加载自定义内置函数,从而可以取消设置只读属性。做到这一点的代码要点:

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使用它。


1

您不能从的手册页unset

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


2
typeset +r VAR根据手册页,我不明白的是为什么为什么不起作用Using '+' instead of '-' turns off the attribute instead, with the exception that +a may not be used to destroy an array variable.
Trebor Rude 2014年

1

在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

尽管这不是范围内的“未设置”,这可能是原始作者的意图,但这绝对是从baz()角度将变量设置为只读,然后再从baz()角度使其变为读写状态。在baz()的视图中,您只需要写一些预想的脚本即可。


1

没有GDB外部二进制文件的另一种解决方案(实际上是强调Graham Nicholls的评论)将是使用exec

就我而言,其中设置了一个烦人的只读变量 /etc/profile.d/xxx

引用bash手册:

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

当启动非登录外壳的交互式外壳时,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不会自动设置“烦人变量”或在支票上设置其他变量,例如:

if [ -n "$annoying_variable" ] && [ "${SHLVL:-1}" = 1 ]
then exec env annoying_variable='' SHLVL=$((SHLVL+1)) ${SHELL:-/bin/bash}
fi
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.