как отключить переменную readonly в Bash?
$ readonly PI=3.14
$ unset PI
bash: PI: readonly variable
или это невозможно?
как отключить переменную readonly в Bash?
$ readonly PI=3.14
$ unset PI
bash: PI: readonly variable
или это невозможно?
Собственно, вы можете отключить переменную 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
$
Я попробовал взломать gdb выше, потому что я хочу отключить 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
Согласно справочной странице:
unset [-fv] [name ...]
... Read-only variables may not be
unset. ...
Если вы еще не экспортировали переменную, вы можете использовать exec "$0" "[email protected]"
для перезапуска вашей оболочки, конечно, вы потеряете и все другие неэкспортированные переменные. Кажется, что если вы запускаете новую оболочку без exec
, она теряет свойство только для чтения для этой оболочки.
Но с более простым синтаксисом:
gdb -ex 'call unbind_variable("PI")' --pid=$$ --batch
С некоторым улучшением, как функция:
destroy
:Или Как играть с переменными метаданными. Обратите внимание на использование редких ударов: local -n VARIABLE=$1
и ${[email protected]}
...
destroy () {
local -n variable=$1
declare -p $1 &>/dev/null || return -1 # Return if variable not exist
local reslne result flags=${[email protected]}
[ -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=${[email protected]} 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
, если флаг только для чтения отсутствуетwhile read ... result= ... done
получают код возврата call unbind
на выходе gdb
gdb
синтаксис с использованием --pid
и --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 ${[email protected]} # 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 ${[email protected]} # 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
В частности, ответьте на переменную TMOUT. Другой вариант, если gdb недоступен, - это скопировать bash в ваш домашний каталог и исправить строку TMOUT в двоичном файле на что-то еще, например XMOUX. А затем запустите этот дополнительный слой оболочки, и вы не будете время ожидания.
Использование 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... ;-)
readonly делает ее окончательной и постоянной, пока процесс оболочки не завершится. Если вам нужно изменить переменную, не отмечайте ее только для чтения.
Нет, не в текущей оболочке. Если вы хотите присвоить ему новое значение, вам придется разблокировать новую оболочку, где она будет иметь новое значение и не будет считаться read only
.
$ { ( readonly pi=3.14; echo $pi ); pi=400; echo $pi; unset pi; echo [$pi]; }
3.14
400
[]
В zsh,
$ typeset +r PI
(Да, я знаю, что вопрос говорит bash. Но когда вы Google для zsh, вы также получаете кучу вопросов bash.)
На странице руководства unset
вы не можете:
Для каждого имени удалите соответствующую переменную или функцию. Если параметры не заданы или не указана опция -v, каждое имя относится к переменной оболочки. Переменные только для чтения не могут быть отменены. Если задано -f, каждое имя относится к функции оболочки, а определение функции удаляется. Каждая незанятая переменная или функция удаляется из среды, переданной в последующие команды. Если любые из RANDOM, SECONDS, LINENO, HISTCMD, FUNCNAME, GROUPS или DIRSTACK не установлены, они теряют свои особые свойства, даже если они впоследствии reset. Статус выхода - true, если только имя не указано только.
Еще один способ "сбросить" переменную, доступную только для чтения, в 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
bar;
PI=3.1415927
Хотя это и не "отключение" в области видимости, что, вероятно, и было целью первоначального автора, это определенно устанавливает переменную только для чтения с точки зрения baz(), а затем делает ее доступной для чтения-записи с точки зрения view baz(), вам просто нужно написать свой сценарий с некоторой продуманностью.
$ 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
$
Подоболочка может наследовать родительские переменные, но не наследует их защищенный статус.
Альтернатива, если gdb недоступен: вы можете использовать команду enable
, чтобы загрузить пользовательскую встроенную функцию, которая позволит вам сбросить атрибут только для чтения. Суть кода, который делает это:
SETVARATTR (find_variable ("TMOUT"), att_readonly, 1);
Очевидно, вы бы заменили TMOUT
на переменную, которая вас интересует.
Если вы не хотите превращать это во встроенную функцию самостоятельно, я раздвоил bash в GitHub и добавил полностью написанную и готовую к компиляции загружаемую встроенную систему под названием readwrite
. Коммит находится на https://github.com/josephcsible/bash/commit/bcec716f4ca958e9c55a976050947d2327bcc195. Если вы хотите использовать его, возьмите исходный код Bash с моим коммитом, запустите ./configure && make loadables
, чтобы построить его, затем enable -f examples/loadables/readwrite readwrite
, чтобы добавить его в текущий сеанс, затем readwrite TMOUT
, чтобы использовать его.