簡體   English   中英

在 bash 中取消設置只讀變量

[英]Unset readonly variable in bash

如何取消設置 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

$

oneliner 的答案是使用批處理模式和其他命令行標志,如F. Hauri 的回答中提供

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

根據內核的 ptrace_scope 設置,可能需要也可能不需要sudo 查看有關 vip9937 答案的評論以獲取更多詳細信息。

我嘗試了上面的 gdb hack,因為我想取消設置 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

在 zsh 中,

% typeset +r PI
% unset PI

(是的,我知道這個問題說的是 bash。但是當你谷歌搜索 zsh 時,你也會得到一堆 bash 問題。)

根據手冊頁:

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

如果你還沒有導出變量,你可以使用exec "$0" "$@"重新啟動你的 shell,當然你也會丟失所有其他未導出的變量。 似乎如果你在沒有exec情況下啟動一個新的 shell,它會失去該 shell 的只讀屬性。

使用 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 一樣危險...... ;-)

很快:靈感來自anishsane 的回答

但使用更簡單的語法:

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

隨着一些改進,作為一個功能:

我的destroy功能:

如何使用可變元數據 注意罕見的bashisms用法: 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
}

您可以將其復制到名為destroy.bashbash 源文件中,例如...

解釋:

 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
  • 如果readonly 標志不存在,第 5 到 8 行將運行unset而不是gdb
  • 第 9 行到第 12 行while read ... result= ... donegdb輸出中獲取call unbind返回碼
  • 使用--pid--ex第 13 行gdb語法(請參閱gdb --help )。
  • 第 15 行返回call unbind命令的$result

使用中:

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。 然后運行這個額外的shell層,你就不會超時了。

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

子shell 可以繼承父級的變量,但不會繼承其受保護的狀態。

readonly 命令使其成為最終和永久的,直到 shell 進程終止。 如果您需要更改變量,請不要將其標記為只讀。

不,不在當前的 shell 中。 如果你想給它分配一個新的值,你必須創建一個新的 shell,它會有一個新的含義並且不會被認為是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

如果您不想自己將其轉換為內置函數,我在 GitHub 中分叉了 bash 並添加了一個完整編寫且准備編譯的可加載內置readwrite 提交位於https://github.com/josephcsible/bash/commit/bcec716f4ca958e9c55a976050947d2327bcc195 如果你想使用它,用我的提交獲取 Bash 源,運行./configure && make loadables來構建它,然后enable -f examples/loadables/readwrite readwrite將它添加到你的運行會話,然后readwrite TMOUT使用它.

你不能,從unset手冊頁:

對於每個名稱,刪除相應的變量或函數。 如果未提供任何選項,或提供了 -v 選項,則每個名稱都引用一個 shell 變量。 只讀變量不能取消設置。 如果指定了 -f,則每個名稱都引用一個 shell 函數,並刪除函數定義。 每個未設置的變量或函數都會從傳遞給后續命令的環境中刪除。 如果 RANDOM、SECONDS、LINENO、HISTCMD、FUNCNAME、GROUPS 或 DIRSTACK 中的任何一個未設置,它們將失去其特殊屬性,即使它們隨后被重置。 除非名稱為只讀,否則退出狀態為真。

在 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外部二進制文件的解決方案(實際上強調Graham Nicholls評論)是使用exec

就我而言, /etc/profile.d/xxx設置了一個煩人的只讀變量。

引用 bash 手冊:

“當 bash 作為交互式登錄 shell [...] 被調用時,它首先從文件 /etc/profile 讀取並執行命令”[...]

當一個不是登錄 shell 的交互式 shell 啟動時,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 ✔️

筆記:

  1. 使用bash 5.0.17gdb 10.1測試。
  2. -v varname測試是在bash 4.2中添加的。 “如果 shell 變量varname已設置(已分配值),則為True 。” bash 參考手冊
  3. 注意轉換為int 否則,將導致以下錯誤: 'unbind_variable' has unknown return type; cast the call to its declared return type '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錯誤。

如果沒有任何幫助,您可以 go 回到過去,回到尚未實現只讀變量的時間:

env ENV=$HOME/.profile /bin/sh

並在 $HOME/.profile 中表現出一些善意並說

export TMOUT=901

這會在您注銷前多給您一秒鍾:-)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM