Як зняти змінну лише для читання в 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.
Відповіді:
Насправді ви можете скасувати змінну лише для читання . але я повинен попередити, що це хакерський метод. Додаючи цю відповідь, лише як інформацію, а не як рекомендацію. Використовуйте його на свій страх і ризик. Перевірено на 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, щоб отримати докладнішу інформацію.
cat << EOF| sudo gdb
на sudo gdb << EOF
. Це може не спрацювати, оскільки переспрямований постачальник введення - bash
зупиняється через gdb
вкладення.
echo -e "attach $$\n call unbind_variable(\"PI\")\n detach" | gdb
Я спробував 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 на безпеку.
/proc/sys/kernel/yama/ptrace_scope
. Найпоширенішими значеннями є 0
, в цьому випадку ви можете це зробити, і 1
, в цьому випадку ви, мабуть, не можете, оскільки gdb
не є прямим батьківським bash
процесом, який налагоджується .
-q
і -n
є корисними, вони (а саме -q
) не замовчують gdb
, тому /dev/null
переспрямування все ще потрібне. Хоча чудова пропозиція, @LucasCimon
Використання 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
.
У zsh,
% typeset +r PI
% unset PI
(Так, я знаю, що в запитанні написано bash. Але коли ви шукаєте Google за zsh, ви також отримуєте купу запитань bash.)
Відповідно до сторінки користувача:
unset [-fv] [name ...]
... Read-only variables may not be
unset. ...
Якщо ви ще не експортували змінну, ви можете використовувати її exec "$0" "$@"
для перезапуску оболонки, звичайно, ви втратите і всі інші не експортовані змінні. Здається, якщо запустити нову оболонку без exec
, вона втрачає властивість лише для читання для цієї оболонки.
Але з спрощеним синтаксисом:
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 }
$flags
.unset
замість того, gdb
якщо прапор лише для читання відсутнійwhile read ... result= ... done
отримують код повернення call unbind
в gdb
вихідному кодіgdb
синтаксис рядка 13 із використанням --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
Зокрема wrt до змінної TMOUT. Інший варіант, якщо gdb недоступний, - це скопіювати bash у свій домашній каталог і закріпити рядок TMOUT у двійковому файлі чимось іншим, наприклад XMOUX. А потім запустіть цей додатковий шар оболонки, і у вас не буде тайм-ауту.
Команда 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/josephcsible/bash/commit/bcec716f4ca958e9c55a976050947d2327bcc195 . Якщо ви хочете ним скористатися, отримайте джерело Bash разом із моїм комітом, запустіть ./configure && make loadables
його побудову, потім enable -f examples/loadables/readwrite readwrite
додайте його до запущеного сеансу, а потім readwrite TMOUT
використайте.
Ви не можете, зі сторінки керівництва unset
:
Для кожного імені видаліть відповідну змінну або функцію. Якщо не вказано жодної опції або вказано параметр -v, кожне ім'я посилається на змінну оболонки. Змінні, призначені лише для читання, можуть не бути скасовані. Якщо вказано -f, кожне ім'я посилається на функцію оболонки, а визначення функції видаляється. Кожна незмінена змінна або функція видаляється із середовища, передається наступним командам. Якщо будь-який із СЛУЧАЙНИХ, СЕКУНД, 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.
Ще один спосіб "зняти" змінну лише для читання в 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 (), вам просто потрібно написати свій сценарій з певним продумуванням.
Іншим рішенням без 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