如何在Bash中取消设置只读变量?
$ readonly PI=3.14
$ unset PI
bash: PI: readonly variable
还是不可能?
如何在Bash中取消设置只读变量?
$ readonly PI=3.14
$ unset PI
bash: PI: readonly variable
还是不可能?
readonly TMOUT
。我喜欢注释这些行,并打开与该Linux机器的新连接。
Answers:
实际上,您可以取消设置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答案的评论。
cat << EOF| sudo gdb
为sudo gdb << EOF
。它可能 没有工作,因为重定向输入提供商-bash
正由于停止gdb
附件。
echo -e "attach $$\n call unbind_variable(\"PI\")\n detach" | gdb
我尝试了上面的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
-q -n
选项来使每个安全性gdb
均不加载任何.gdbinit文件。
/proc/sys/kernel/yama/ptrace_scope
进行控制。最常见的值是0
,在这种情况下,您可以执行此操作;1
在这种情况下,您可能不能这样做,因为gdb
它不是bash
要调试的进程的直接父级。
-q
并且-n
很有帮助,但它们(即-q
)不会保持沉默gdb
,因此/dev/null
仍然需要重定向。不过,这是一个很好的建议,@ LucasCimon
使用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一样危险... ;-)
include ctypes.sh
你的意思source ctypes.sh
或. ctypes.sh
。
但是使用更简单的语法:
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 }
$flags
。unset
而不是gdb
if readonly标志不存在while read ... result= ... done
获取call unbind
in的返回码gdb
输出gdb
语法,使用--pid
and --ex
(请参阅gdb --help
)。$result
的call 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
readonly命令使它成为永久的永久性操作,直到Shell进程终止。如果您需要更改变量,请不要将其标记为只读。
不,不在当前shell中。如果您希望为其分配新值,则必须在其中具有新含义且不会被视为的新shell上派生read only
。
$ { ( readonly pi=3.14; echo $pi ); pi=400; echo $pi; unset pi; echo [$pi]; }
3.14
400
[]
如果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
使用它。
您不能从的手册页unset
:
对于每个名称,删除相应的变量或函数。如果未提供任何选项,或提供了-v选项,则每个名称都引用一个shell变量。 只读变量可能未设置。 如果指定了-f,则每个名称都引用一个shell函数,并且该函数定义将被删除。每个未设置的变量或函数都将从传递给后续命令的环境中删除。如果未设置RANDOM,SECONDS,LINENO,HISTCMD,FUNCNAME,GROUPS或DIRSTACK中的任何一个,则即使随后将其重置,它们也会丢失其特殊属性。除非名称为只读,否则退出状态为true。
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.
没有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