Как мне отключить переменную только для чтения в Bash?
$ readonly PI=3.14
$ unset PI
bash: PI: readonly variable
или это невозможно?
Как мне отключить переменную только для чтения в Bash?
$ readonly PI=3.14
$ unset PI
bash: PI: readonly variable
или это невозможно?
Фактически, вы можете отключить переменную только для чтения. но я должен предупредить, что это хакерский метод. Добавляю этот ответ только как информацию, а не как рекомендацию. Используйте его на свой страх и риск. Проверено на 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. Ответ Хаури:
$ sudo gdb -ex 'call unbind_variable("PI")' --pid=$$ --batch
sudo
может потребоваться, а может и не потребоваться в зависимости от настроек ptrace_scope вашего ядра. Подробнее см. В комментариях к ответу vip9937.
cat << EOF| sudo gdb
на sudo gdb << EOF
. Это может не работать, поскольку перенаправленный поставщик ввода - bash
остановлен из-за gdb
вложения.
- person anishsane; 13.08.2014
gdb
в режиме сценария, может быть полезен, если нужно сделать много attach
. Для процесса uniq используйте --pid
flag ... и для отдельной команды вы можете использовать -ex
, см. мой ответ: что-то вроде gdb -ex 'call unbind_variable("PI")' --pid=$$ --batch
могло быть достаточно.
- person F. Hauri; 24.11.2018
echo -e "attach $$\n call unbind_variable(\"PI\")\n detach" | gdb
- person Satya Mishra; 02.05.2019
Я попробовал взлом 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
-q -n
параметры, чтобы gdb
не загружать файлы .gdbinit из соображений безопасности.
- person Lucas Cimon; 06.11.2014
/proc/sys/kernel/yama/ptrace_scope
. Наиболее распространенными значениями являются 0
, в этом случае вы можете это сделать, и 1
, и в этом случае вы, вероятно, не можете, поскольку gdb
не является прямым родительским элементом bash
отлаживаемого процесса.
- person Eliah Kagan; 10.11.2017
-q
и -n
полезны, они (а именно -q
) не заглушают gdb
, поэтому /dev/null
перенаправление по-прежнему необходимо. Отличное предложение, @LucasCimon
- person fbicknel; 20.12.2017
unset
ее
- person Thamme Gowda; 03.07.2019
В zsh,
% typeset +r PI
% unset PI
(Да, я знаю, что вопрос говорит о bash. Но когда вы используете Google для zsh, вы также получаете кучу вопросов о bash.)
Согласно странице руководства:
unset [-fv] [name ...]
... Read-only variables may not be
unset. ...
Если вы еще не экспортировали переменную, вы можете использовать exec "$0" "$@"
для перезапуска оболочки, конечно, вы потеряете и все другие неэкспортированные переменные. Кажется, если вы запустите новую оболочку без exec
, она потеряет свойство только для чтения для этой оболочки.
Использование GDB ужасно медленное. Вместо этого попробуйте ctypes.sh. Он работает, используя libffi для непосредственного вызова unbind_variable () bash, что во всех случаях так же быстро, как и использование любой другой встроенной функции 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
.
- person Dennis Williamson; 25.11.2018
Но с более простым синтаксисом:
gdb -ex 'call unbind_variable("PI")' --pid=$$ --batch
С некоторыми улучшениями, как функция:
destroy
функция:Или Как играть с переменными метаданными. Обратите внимание на использование редких башизмов: local -n VARIABLE=$1
и _4 _...
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
, если флаг только для чтения отсутствует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 ${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
В частности, по переменной TMOUT. Другой вариант, если gdb недоступен, - скопировать bash в ваш домашний каталог и исправить строку TMOUT в двоичном файле на что-нибудь еще, например XMOUX. А затем запустите этот дополнительный слой оболочки, и у вас не будет тайм-аута.
$ 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
$
Подоболочка может наследовать родительские переменные, но не наследует их защищенный статус.
Команда readonly делает его окончательным и постоянным до завершения процесса оболочки. Если вам нужно изменить переменную, не помечайте ее только для чтения.
Нет, не в текущей оболочке. Если вы хотите присвоить ему новое значение, вам придется создать новую оболочку, в которой он будет иметь новое значение и не будет рассматриваться как 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
переменной, которая вам нужна.
Если вы не хотите превращать это во встроенную программу самостоятельно, я разветвил bash в GitHub и добавил полностью написанную и готовую к компиляции загружаемую встроенную функцию под названием readwrite
. Фиксация находится по адресу https://github.com/./configure && make loadables
, чтобы его построить, затем enable -f examples/loadables/readwrite readwrite
, чтобы добавить его в ваш текущий сеанс, затем readwrite TMOUT
, чтобы использовать его.
Вы не можете, со страницы руководства unset
:
Для каждого имени удалите соответствующую переменную или функцию. Если параметры не указаны или задана опция -v, каждое имя относится к переменной оболочки. Переменные только для чтения не могут быть сброшены. Если указан -f, каждое имя относится к функции оболочки, а определение функции удаляется. Каждая неустановленная переменная или функция удаляется из среды, передаваемой последующим командам. Если какие-либо из RANDOM, SECONDS, LINENO, HISTCMD, FUNCNAME, GROUPS или DIRSTACK не установлены, они теряют свои особые свойства, даже если они впоследствии сбрасываются. Статус выхода истинен, если имя не доступно только для чтения.
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.
- person Trebor Rude; 13.08.2014
Еще один способ "отключить" переменную только для чтения в 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 (), вам просто нужно написать сценарий с некоторой предусмотрительностью.
Другое решение без GDB или внешнего двоичного файла (на самом деле акцент на комментарии Грэма Николлса) будет использованием _ 1_.
В моем случае в /etc/profile.d/xxx
была установлена раздражающая переменная, доступная только для чтения.
Цитата из руководства bash:
«Когда bash вызывается как интерактивная оболочка для входа [...], он сначала читает и выполняет команды из файла / 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
$ 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 ✔️
Примечания:
bash 5.0.17
и gdb 10.1
.-v varname
был добавлен в bash 4.2
. Это True
, если переменная оболочки varname
установлена (ей присвоено значение). - справочное руководство по bashint
. Без этого появится следующая ошибка: 'unbind_variable' has unknown return type; cast the call to its declared return type
. исходный код bash показывает, что тип возврата unbind_variable
функция int
.int
, чтобы обойти ошибку unknown return type
.
readonly TMOUT
. Я предпочитаю прокомментировать эти строки и открыть новое соединение с этой Linux-машиной. - person Elrond_EGLDer   schedule 08.12.2016