[英]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 一样危险...... ;-)
But with simplier syntax:但使用更简单的语法:
gdb -ex 'call unbind_variable("PI")' --pid=$$ --batch
With some improvement, as a function:随着一些改进,作为一个功能:
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.bash
的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 }
$flags
.$flags
。unset
instead of gdb
if readonly flag not presentunset
而不是gdb
while read ... result= ... done
get return code of call unbind
in gdb
outputwhile read ... result= ... done
在gdb
输出中获取call unbind
返回码gdb
syntax with use of --pid
and --ex
(see gdb --help
).--pid
和--ex
第 13 行gdb
语法(请参阅gdb --help
)。$result
of call unbind
command.call unbind
命令的$result
。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:笔记:
bash 5.0.17
and gdb 10.1
.bash 5.0.17
和gdb 10.1
测试。-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)." varname
已设置(已分配值),则为True
。” – bash reference manual 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
. unbind_variable
函数的返回类型是int
。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.