Скасовано змінну лише для читання в bash


78

Як зняти змінну лише для читання в Bash?

$ readonly PI=3.14

$ unset PI
bash: PI: readonly variable

чи це не можливо?


ах мій поганий tldp.org/LDP/Bash-Beginners-Guide/html/sect_10_01.html Зробити змінні лише для читання. Потім цим змінним неможливо присвоїти значення за допомогою наступних операторів присвоєння, а також їх не можна скасувати.
Kokizzu

Зазвичай змінні читаються лише тому, що / etc / profile містить багато таких рядків readonly TMOUT. Я вважаю за краще коментувати ці рядки і відкривати нове підключення до цієї машини Linux.
ROMANIA_engineer

1
@ROMANIA_engineer Або просто виконайте bash --norc, а потім встановіть потрібний матеріал вручну або у своєму власному файлі rc - наприклад: source ~ / .gnbashrc
Грехем Ніколлс

Відповіді:


108

Насправді ви можете скасувати змінну лише для читання . але я повинен попередити, що це хакерський метод. Додаючи цю відповідь, лише як інформацію, а не як рекомендацію. Використовуйте його на свій страх і ризик. Перевірено на 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

$

Відповідь oneliner полягає у використанні пакетного режиму та інших прапорів командного рядка, як передбачено у відповіді Ф. Хаурі :

$ sudo gdb -ex 'call unbind_variable("PI")' --pid=$$ --batch

sudoможе знадобитися чи не знати на основі налаштувань ptrace_scope вашого ядра. Перегляньте коментарі до відповіді vip9937, щоб отримати докладнішу інформацію.


53
Зараз це те, що я б назвав програмуванням redheck bash;)
Флойд,

6
Примітка: Не спокушайтеся змінити cat << EOF| sudo gdbна sudo gdb << EOF. Це може не спрацювати, оскільки переспрямований постачальник введення - bashзупиняється через gdbвкладення.
anishsane

Я думаю, що ця відповідь трохи точніша, оскільки вона не вимагає sudo і закінчує gdb належним чином
Rafareino

1
^^ EOF при stdin & явному виході обидва вийшли б з gdb чисто.
anishsane

2
Мені подобається один лайнер:echo -e "attach $$\n call unbind_variable(\"PI\")\n detach" | gdb
Сатья Мішра,

48

Я спробував 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

3
Я б запропонував використовувати -q -nпараметри, щоб заглушити gdbне завантажувати будь- який файл .gdbinit на безпеку.
Лукас Сімон,

3
"оскільки я є власником процесу bash, мені не потрібен sudo" Зверніть увагу, що це залежить від того, яку операційну систему ви використовуєте та як вона налаштована. У більшості використовуваних в даний час версій ядра Linux це контролюється/proc/sys/kernel/yama/ptrace_scope . Найпоширенішими значеннями є 0, в цьому випадку ви можете це зробити, і 1, в цьому випадку ви, мабуть, не можете, оскільки gdbне є прямим батьківським bashпроцесом, який налагоджується .
Еліа Каган,

Хоча -qі -nє корисними, вони (а саме -q) не замовчують gdb, тому /dev/nullпереспрямування все ще потрібне. Хоча чудова пропозиція, @LucasCimon
fbicknel

будь-які ідеї, як зробити щось подібне на машині без gdb?
lightswitch05,

1
@ lightswitch05: див. мою відповідь на ctypes.sh
Віль,

6

Використання 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 ... ;-)


1
Я припускаю, де ви говорите, що include ctypes.shмаєте на увазі source ctypes.shабо . ctypes.sh.
Денніс Вільямсон


5

Відповідно до сторінки користувача:

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

Якщо ви ще не експортували змінну, ви можете використовувати її exec "$0" "$@"для перезапуску оболонки, звичайно, ви втратите і всі інші не експортовані змінні. Здається, якщо запустити нову оболонку без exec, вона втрачає властивість лише для читання для цієї оболонки.


перезапуск оболонки в кращому випадку є схематичним
Ерік

5

Коротко: натхненний відповіддю anishsane

Але з спрощеним синтаксисом:

gdb -ex 'call unbind_variable("PI")' --pid=$$ --batch

З деяким покращенням, як функція:

Моя destroyфункція:

Або як грати зі змінними метаданими . Зверніть увагу на використання рідкісних башизмів : 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  }
  • рядок 2 створює локальне посилання на передану змінну.
  • рядок 3 запобігає запуску на неіснуючій змінній
  • рядок 4 зберігає атрибути параметра (мета) у $flags.
  • рядки 5-8 будуть виконуватися unsetзамість того, gdbякщо прапор лише для читання відсутній
  • рядки 9-12 while read ... result= ... doneотримують код повернення call unbindв gdbвихідному коді
  • gdbсинтаксис рядка 13 із використанням --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

4

Зокрема wrt до змінної TMOUT. Інший варіант, якщо gdb недоступний, - це скопіювати bash у свій домашній каталог і закріпити рядок TMOUT у двійковому файлі чимось іншим, наприклад XMOUX. А потім запустіть цей додатковий шар оболонки, і у вас не буде тайм-ауту.


2
Навіть більше зла, ніж хак gdb. Тож ... +1!
Алоїс Махдал,

3

Команда readonly робить його остаточним і постійним до завершення процесу оболонки. Якщо вам потрібно змінити змінну, не позначайте її лише для читання.


2

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

$ { ( readonly pi=3.14; echo $pi ); pi=400; echo $pi; unset pi; echo [$pi]; }
3.14
400
[]

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

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


2

Альтернатива , якщо 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використайте.


1

Ви не можете, зі сторінки керівництва unset:

Для кожного імені видаліть відповідну змінну або функцію. Якщо не вказано жодної опції або вказано параметр -v, кожне ім'я посилається на змінну оболонки. Змінні, призначені лише для читання, можуть не бути скасовані. Якщо вказано -f, кожне ім'я посилається на функцію оболонки, а визначення функції видаляється. Кожна незмінена змінна або функція видаляється із середовища, передається наступним командам. Якщо будь-який із СЛУЧАЙНИХ, СЕКУНД, LINENO, HISTCMD, FUNCNAME, GROUPS або DIRSTACK вимкнено, вони втрачають свої особливі властивості, навіть якщо згодом їх скинути. Статус виходу істинний, якщо ім'я не лише для читання.


2
Я не розумію, чому 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.
Требор Руде

1

Ще один спосіб "зняти" змінну лише для читання в 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 (), а потім надає їй можливість читати і писати з точки view of baz (), вам просто потрібно написати свій сценарій з певним продумуванням.


1

Іншим рішенням без GDB або зовнішнього двійкового файлу (насправді наголос на коментарі Грехема Ніколлса ) було б використання exec.

У моєму випадку була встановлена ​​надокучлива змінна лише для читання /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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.