Отключить переменную только для чтения в bash

Как мне отключить переменную только для чтения в Bash?

$ readonly PI=3.14

$ unset PI
bash: PI: readonly variable

или это невозможно?


person Kokizzu    schedule 01.07.2013    source источник
comment
ах мой плохой tldp.org/LDP/Bash-Beginners-Guide/html /sect_10_01.html Сделать переменные доступными только для чтения. Этим переменным не могут быть присвоены значения последующими операторами присваивания, и они не могут быть сброшены.   -  person Kokizzu    schedule 01.07.2013
comment
Обычно переменные доступны только для чтения, потому что / etc / profile содержит много таких строк readonly TMOUT. Я предпочитаю прокомментировать эти строки и открыть новое соединение с этой Linux-машиной.   -  person Elrond_EGLDer    schedule 08.12.2016
comment
@ROMANIA_engineer Или просто exec bash --norc, а затем установите то, что вам нужно, вручную или в своем собственном rc-файле, например: source ~ / .gnbashrc   -  person Graham Nicholls    schedule 06.02.2019


Ответы (15)


Фактически, вы можете отключить переменную только для чтения. но я должен предупредить, что это хакерский метод. Добавляю этот ответ только как информацию, а не как рекомендацию. Используйте его на свой страх и риск. Проверено на 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.

person anishsane    schedule 01.07.2013
comment
Вот это я бы назвал бы программированием на bash;) - person Floyd; 29.12.2013
comment
Примечание. Не поддавайтесь соблазну изменить cat << EOF| sudo gdb на sudo gdb << EOF. Это может не работать, поскольку перенаправленный поставщик ввода - bash остановлен из-за gdb вложения. - person anishsane; 13.08.2014
comment
Я думаю, что этот ответ немного более точен, так как он не требует sudo и правильно завершает gdb - person Rafareino; 19.08.2015
comment
^^ EOF при стандартном вводе и явном выходе из GDB завершится без ошибок. - person anishsane; 20.08.2015
comment
@anishsane, использующий gdb в режиме сценария, может быть полезен, если нужно сделать много attach. Для процесса uniq используйте --pid flag ... и для отдельной команды вы можете использовать -ex, см. мой ответ: что-то вроде gdb -ex 'call unbind_variable("PI")' --pid=$$ --batch могло быть достаточно. - person F. Hauri; 24.11.2018
comment
^^ О да, это действительно удобно. :-) - person anishsane; 25.11.2018
comment
Мне нравятся лайнеры: 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
person vip9937    schedule 22.01.2014
comment
Я бы предложил использовать -q -n параметры, чтобы gdb не загружать файлы .gdbinit из соображений безопасности. - person Lucas Cimon; 06.11.2014
comment
поскольку я владею процессом bash, мне не нужно sudo Обратите внимание, что это зависит от того, какую операционную систему вы используете и как она настроена. Для большинства используемых в настоящее время версий ядра Linux это контролируется через /proc/sys/kernel/yama/ptrace_scope. Наиболее распространенными значениями являются 0, в этом случае вы можете это сделать, и 1, и в этом случае вы, вероятно, не можете, поскольку gdb не является прямым родительским элементом bash отлаживаемого процесса. - person Eliah Kagan; 10.11.2017
comment
Хотя -q и -n полезны, они (а именно -q) не заглушают gdb, поэтому /dev/null перенаправление по-прежнему необходимо. Отличное предложение, @LucasCimon - person fbicknel; 20.12.2017
comment
есть идеи, как сделать что-то подобное на машине без GDB? - person lightswitch05; 04.05.2018
comment
@ lightswitch05: см. мой ответ ctypes.sh - person Wil; 13.06.2018
comment
я приветствую тебя????????. Меня постоянно раздражала эта переменная TMOUT, доступная только для чтения, и меня разочаровывала моя неспособность unset ее - person Thamme Gowda; 03.07.2019

В zsh,

% typeset +r PI
% unset PI

(Да, я знаю, что вопрос говорит о bash. Но когда вы используете Google для zsh, вы также получаете кучу вопросов о bash.)

person Radon Rosborough    schedule 24.08.2017
comment
Работает! Для получения дополнительной информации: zsh.sourceforge.net/Doc/Release/Shell- Builtin-Commands.html. Спасибо! - person Wellington1993; 22.04.2019

Согласно странице руководства:

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

Если вы еще не экспортировали переменную, вы можете использовать exec "$0" "$@" для перезапуска оболочки, конечно, вы потеряете и все другие неэкспортированные переменные. Кажется, если вы запустите новую оболочку без exec, она потеряет свойство только для чтения для этой оболочки.

person Kevin    schedule 01.07.2013
comment
перезапуск оболочки в лучшем случае отрывочен - person Eric; 01.01.2021

Использование 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 ... ;-)

person Wil    schedule 25.04.2018
comment
Я предполагаю, где вы говорите include ctypes.sh, вы имеете в виду source ctypes.sh или . ctypes.sh. - person Dennis Williamson; 25.11.2018

Вкратце: вдохновлено ответом anishsane

Но с более простым синтаксисом:

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  }
  • строка 2 создает локальную ссылку на отправленную переменную.
  • строка 3 предотвращает запуск несуществующей переменной
  • строка 4 сохраняет атрибуты параметра (мета) в $flags.
  • строки с 5 по 8 будут запускать unset вместо gdb, если флаг только для чтения отсутствует
  • строки с 9 по 12 while read ... result= ... done получают код возврата call unbind в gdb выводе
  • строка 13 gdb синтаксис с использованием --pid и --ex (см. gdb --help).
  • строка 15 возвращает $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
person F. Hauri    schedule 24.11.2018

В частности, по переменной TMOUT. Другой вариант, если gdb недоступен, - скопировать bash в ваш домашний каталог и исправить строку TMOUT в двоичном файле на что-нибудь еще, например XMOUX. А затем запустите этот дополнительный слой оболочки, и у вас не будет тайм-аута.

person user1089933    schedule 20.01.2015
comment
Даже более злобный, чем взлом GDB. Итак ... +1! - person Alois Mahdal; 12.05.2017

$ 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
$

Подоболочка может наследовать родительские переменные, но не наследует их защищенный статус.

person jezzaaaa    schedule 15.02.2019
comment
Спасибо! Это привело к простому способу отключения TMOUT. Измените раздел ~ / .ssh / config Host, чтобы RemoteCommand выполнял $ {BASH}. - person Les Grieve; 23.06.2021

Команда readonly делает его окончательным и постоянным до завершения процесса оболочки. Если вам нужно изменить переменную, не помечайте ее только для чтения.

person Amit Verma    schedule 01.07.2013

Нет, не в текущей оболочке. Если вы хотите присвоить ему новое значение, вам придется создать новую оболочку, в которой он будет иметь новое значение и не будет рассматриваться как read only.

$ { ( readonly pi=3.14; echo $pi ); pi=400; echo $pi; unset pi; echo [$pi]; }
3.14
400
[]
person jaypal singh    schedule 01.07.2013

Альтернатива, если gdb недоступен: вы можете использовать команда enable для загрузки настраиваемая встроенная функция, которая позволит вам отключить атрибут только для чтения. Суть кода, который это делает:

SETVARATTR (find_variable ("TMOUT"), att_readonly, 1);

Очевидно, вы должны заменить TMOUT переменной, которая вам нужна.

Если вы не хотите превращать это во встроенную программу самостоятельно, я разветвил bash в GitHub и добавил полностью написанную и готовую к компиляции загружаемую встроенную функцию под названием readwrite. Фиксация находится по адресу https://github.com/. Если вы хотите его использовать, получите исходный код Bash с моим коммитом, запустите ./configure && make loadables, чтобы его построить, затем enable -f examples/loadables/readwrite readwrite, чтобы добавить его в ваш текущий сеанс, затем readwrite TMOUT, чтобы использовать его.

person Joseph Sible-Reinstate Monica    schedule 11.10.2019

Вы не можете, со страницы руководства unset:

Для каждого имени удалите соответствующую переменную или функцию. Если параметры не указаны или задана опция -v, каждое имя относится к переменной оболочки. Переменные только для чтения не могут быть сброшены. Если указан -f, каждое имя относится к функции оболочки, а определение функции удаляется. Каждая неустановленная переменная или функция удаляется из среды, передаваемой последующим командам. Если какие-либо из RANDOM, SECONDS, LINENO, HISTCMD, FUNCNAME, GROUPS или DIRSTACK не установлены, они теряют свои особые свойства, даже если они впоследствии сбрасываются. Статус выхода истинен, если имя не доступно только для чтения.

person Yu Hao    schedule 01.07.2013
comment
Я не понимаю, почему 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 (), вам просто нужно написать сценарий с некоторой предусмотрительностью.

person Wil    schedule 21.03.2018

Другое решение без 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
person bufh    schedule 12.12.2019

$ 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 ✔️

Примечания:

  1. Протестировано с bash 5.0.17 и gdb 10.1.
  2. Тест -v varname был добавлен в bash 4.2. Это True, если переменная оболочки varname установлена ​​(ей присвоено значение). - справочное руководство по bash
  3. Обратите внимание на приведение к int. Без этого появится следующая ошибка: 'unbind_variable' has unknown return type; cast the call to its declared return type. исходный код bash показывает, что тип возврата unbind_variable функция int.
  4. Этот ответ по сути совпадает с ответом на superuser.com. Я добавил преобразование в int, чтобы обойти ошибку unknown return type.
person Robin A. Meade    schedule 12.05.2021