简体   繁体   English

在 bash 中取消设置只读变量

[英]Unset readonly variable in bash

How do I unset a readonly variable in Bash?如何取消设置 Bash 中的只读变量?

$ readonly PI=3.14

$ unset PI
bash: PI: readonly variable

or is it not possible?还是不可能?

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.在 ubuntu 13.04、bash 4.2.45 上测试。

This method involves knowing a bit of bash source code & it's inherited from this answer.这种方法涉及了解一些 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

$

A oneliner answer is to use the batch mode and other commandline flags, as provided in F. Hauri's answer : oneliner 的答案是使用批处理模式和其他命令行标志,如F. Hauri 的回答中提供

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

sudo may or may not be needed based on your kernel's ptrace_scope settings.根据内核的 ptrace_scope 设置,可能需要也可能不需要sudo Check the comments on vip9937's answer for more details.查看有关 vip9937 答案的评论以获取更多详细信息。

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.我尝试了上面的 gdb hack,因为我想取消设置 TMOUT(以禁用自动注销),但是在将 TMOUT 设置为只读的机器上,我不允许使用 sudo。 But since I own the bash process, I don't need sudo.但是由于我拥有 bash 进程,所以我不需要 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):不过,这确实有效(我把它放在我的 .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

In zsh,在 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.) (是的,我知道这个问题说的是 bash。但是当你谷歌搜索 zsh 时,你也会得到一堆 bash 问题。)

According to the man page:根据手册页:

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

If you have not yet exported the variable, you can use exec "$0" "$@" to restart your shell, of course you will lose all other un-exported variables as well.如果你还没有导出变量,你可以使用exec "$0" "$@"重新启动你的 shell,当然你也会丢失所有其他未导出的变量。 It seems if you start a new shell without exec , it loses its read-only property for that shell.似乎如果你在没有exec情况下启动一个新的 shell,它会失去该 shell 的只读属性。

Using GDB is terribly slow.使用 GDB 非常慢。 Try ctypes.sh instead.试试 ctypes.sh。 It works by using libffi to directly call bash's unbind_variable() instead, which is every bit as fast as using any other bash builtin:它通过使用 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

First you will need to install ctypes.sh:首先,您需要安装 ctypes.sh:

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

See https://github.com/taviso/ctypes.sh for a full description and docs.有关完整说明和文档,请参阅https://github.com/taviso/ctypes.sh

For the curious, yes this lets you call any function within bash, or any function in any library linked to bash, or even any external dynamically-loaded library if you like.出于好奇,是的,这让您可以调用 bash 中的任何函数,或者任何链接到 bash 的库中的任何函数,或者如果您愿意,甚至可以调用任何外部动态加载的库。 Bash is now every bit as dangerous as perl... ;-) Bash 现在和 perl 一样危险...... ;-)

Shortly: inspired by anishsane's answer很快:灵感来自anishsane 的回答

But with simplier syntax:但使用更简单的语法:

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

With some improvement, as a function:随着一些改进,作为一个功能:

My destroy function:我的destroy功能:

Or How to play with variable meta data .如何使用可变元数据 Note usage of rare bashisms : local -n VARIABLE=$1 and ${VARIABLE@a} ...注意罕见的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
}

You could copy this to a bash source file called destroy.bash , for sample...您可以将其复制到名为destroy.bashbash 源文件中,例如...

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 unbind_variable("'$1'")' --pid=$$ --batch 14 ) 15 return $result 16 }
  • line 2 create a local reference to submited variable.第 2 行创建提交变量的本地引用
  • line 3 prevent running on non existant variable第 3 行防止在不存在的变量上运行
  • line 4 store parameter's attributes (meta) into $flags .第 4 行将参数的属性(元)存储到$flags
  • lines 5 to 8 will run unset instead of gdb if readonly flag not present如果readonly 标志不存在,第 5 到 8 行将运行unset而不是gdb
  • lines 9 to 12 while read ... result= ... done get return code of call unbind in gdb output第 9 行到第 12 行while read ... result= ... donegdb输出中获取call unbind返回码
  • line 13 gdb syntax with use of --pid and --ex (see gdb --help ).使用--pid--ex第 13 行gdb语法(请参阅gdb --help )。
  • line 15 return $result of call unbind command.第 15 行返回call unbind命令的$result

In use:使用中:

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

Specifically wrt to the TMOUT variable.特别是写入 TMOUT 变量。 Another option if gdb is not available is to copy bash to your home directory and patch the TMOUT string in the binary to something else, for instance XMOUX.如果 gdb 不可用,另一个选择是将 bash 复制到您的主目录并将二进制文件中的 TMOUT 字符串修补为其他内容,例如 XMOUX。 And then run this extra layer of shell and you will not be timed out.然后运行这个额外的shell层,你就不会超时了。

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

What to do now?现在该怎么办?

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

A subshell can inherit the parent's variables, but won't inherit their protected status.子shell 可以继承父级的变量,但不会继承其受保护的状态。

readonly command makes it final and permanent until the shell process terminates. readonly 命令使其成为最终和永久的,直到 shell 进程终止。 If you need to change a variable, don't mark it readonly.如果您需要更改变量,请不要将其标记为只读。

No, not in the current shell.不,不在当前的 shell 中。 If you wish to assign a new value to it, you will have to fork a new shell where it will have a new meaning and will not be considered as read only .如果你想给它分配一个新的值,你必须创建一个新的 shell,它会有一个新的含义并且不会被认为是read only

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

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.如果 gdb 不可用,另一种方法是:您可以使用enable命令加载一个自定义内置函数,它可以让您取消设置只读属性。 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.显然,您可以用您关心的变量替换TMOUT

If you don't want to turn that into a builtin yourself, I forked bash in GitHub and added a fully-written and ready-to-compile loadable builtin called readwrite .如果您不想自己将其转换为内置函数,我在 GitHub 中分叉了 bash 并添加了一个完整编写且准备编译的可加载内置readwrite The commit is at https://github.com/josephcsible/bash/commit/bcec716f4ca958e9c55a976050947d2327bcc195 .提交位于https://github.com/josephcsible/bash/commit/bcec716f4ca958e9c55a976050947d2327bcc195 If you want to use it, get the Bash source with my commit, run ./configure && make loadables to build it, then enable -f examples/loadables/readwrite readwrite to add it to your running session, then readwrite TMOUT to use it.如果你想使用它,用我的提交获取 Bash 源,运行./configure && make loadables来构建它,然后enable -f examples/loadables/readwrite readwrite将它添加到你的运行会话,然后readwrite TMOUT使用它.

You can't, from manual page of unset :你不能,从unset手册页:

For each name, remove the corresponding variable or function.对于每个名称,删除相应的变量或函数。 If no options are supplied, or the -v option is given, each name refers to a shell variable.如果未提供任何选项,或提供了 -v 选项,则每个名称都引用一个 shell 变量。 Read-only variables may not be unset.只读变量不能取消设置。 If -f is specifed, each name refers to a shell function, and the function definition is removed.如果指定了 -f,则每个名称都引用一个 shell 函数,并删除函数定义。 Each unset variable or function is removed from the environment passed to subsequent commands.每个未设置的变量或函数都会从传递给后续命令的环境中删除。 If any of RANDOM, SECONDS, LINENO, HISTCMD, FUNCNAME, GROUPS, or DIRSTACK are unset, they lose their special properties, even if they are subsequently reset.如果 RANDOM、SECONDS、LINENO、HISTCMD、FUNCNAME、GROUPS 或 DIRSTACK 中的任何一个未设置,它们将失去其特殊属性,即使它们随后被重置。 The exit status is true unless a name is readonly.除非名称为只读,否则退出状态为真。

One other way to "unset" a read-only variable in Bash is to declare that variable read-only in a disposable context:在 Bash 中“取消设置”只读变量的另一种方法是在一次性上下文中将该变量声明为只读:

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

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

foo;

bash: PI: readonly variable bash: PI: 只读变量

bar; 

PI=3.1415927 PI=3.1415927

While this is not "unsetting" within scope, which is probably the intent of the original author, this is definitely setting a variable read-only from the point of view of baz() and then later making it read-write from the point of view of baz(), you just need to write your script with some forethought.虽然这不是在范围内“取消设置”,这可能是原作者的意图,但从 baz() 的角度来看,这绝对是将变量设置为只读,然后从对 baz() 的看法,你只需要事先考虑好编写你的脚本。

Another solution without GDB or an external binary , (in fact an emphasis on Graham Nicholls comment) would be the use of exec .另一个没有GDB外部二进制文件的解决方案(实际上强调Graham Nicholls评论)是使用exec

In my case there were an annoying read-only variable set in /etc/profile.d/xxx .就我而言, /etc/profile.d/xxx设置了一个烦人的只读变量。

Quoting the bash manual:引用 bash 手册:

"When bash is invoked as an interactive login shell [...] it first reads and executes commands from the file /etc/profile" [...] “当 bash 作为交互式登录 shell [...] 被调用时,它首先从文件 /etc/profile 读取并执行命令”[...]

When an interactive shell that is not a login shell is started, bash reads and executes commands from /etc/bash.bashrc [...]当一个不是登录 shell 的交互式 shell 启动时,bash 读取并执行来自 /etc/bash.bashrc [...]

The gist of my workaround was to put in my ~/.bash_profile :我的解决方法的要点是放入我的~/.bash_profile

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

Warning: to avoid a recursion (which would lock you out if you can only access your account through SSH), one should ensure the "annoying variable" will not be automatically set by the bashrc or to set another variable on the check, for example:警告:为了避免递归(如果您只能通过 SSH 访问您的帐户,这会将您锁定),应确保 bashrc 不会自动设置“烦人的变量”或在支票上设置另一个变量,例如:

if [ -n "$annoying_variable" ] && [ "${SHLVL:-1}" = 1 ]
then exec env annoying_variable='' SHLVL=$((SHLVL+1)) ${SHELL:-/bin/bash}
fi
$ 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 ✔️

Notes:笔记:

  1. Tested with bash 5.0.17 and gdb 10.1 .使用bash 5.0.17gdb 10.1测试。
  2. The -v varname test was added in bash 4.2 . -v varname测试是在bash 4.2中添加的。 It is " True if the shell variable varname is set (has been assigned a value)." “如果 shell 变量varname已设置(已分配值),则为True 。” bash reference manual bash 参考手册
  3. Note the cast to int .注意转换为int Without that, the following error will result: 'unbind_variable' has unknown return type; cast the call to its declared return type否则,将导致以下错误: 'unbind_variable' has unknown return type; cast the call to its declared return type 'unbind_variable' has unknown return type; cast the call to its declared return type . 'unbind_variable' has unknown return type; cast the call to its declared return type The bash source code shows that the return type of the unbind_variable function is int . bash 源代码显示unbind_variable函数的返回类型是int
  4. This answer is essentially the same as an answer over at superuser.com .这个答案基本上与superuser.com 上的答案相同。 I added the cast to int to get past the unknown return type error.我将强制转换添加到int以克服unknown return type错误。

if nothing helps, you could go back in time, to a time where readonly vars were not yet implemented:如果没有任何帮助,您可以 go 回到过去,回到尚未实现只读变量的时间:

env ENV=$HOME/.profile /bin/sh

and in $HOME/.profile show some good will and say并在 $HOME/.profile 中表现出一些善意并说

export TMOUT=901

This gives you one extra second before you are logged out:-)这会在您注销前多给您一秒钟:-)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM