【问题标题】:Unset readonly variable in bash在bash中取消设置只读变量
【发布时间】:2013-06-28 03:17:40
【问题描述】:

如何在 Bash 中取消设置只读变量?

$ readonly PI=3.14

$ unset PI
bash: PI: readonly variable

还是不可能?

【问题讨论】:

  • 啊我的坏tldp.org/LDP/Bash-Beginners-Guide/html/sect_10_01.html 将变量设为只读。这些变量不能被后续的赋值语句赋值,也不能被取消设置。
  • 通常变量是只读的,因为 /etc/profile 包含很多类似readonly TMOUT 的行。我更喜欢评论这些行并打开与该 Linux 机器的新连接。
  • @ROMANIA_engineer 或者,简单地执行 bash --norc,然后手动设置你想要的东西,或者在你自己的 rc 文件中 - 例如:source ~/.gnbashrc

标签: bash unset


【解决方案1】:

readonly 命令使它成为最终的和永久的,直到 shell 进程终止。如果您需要更改变量,请不要将其标记为只读。

【讨论】:

    【解决方案2】:

    你不能,来自unset的手册页:

    对于每个名称,删除相应的变量或函数。如果没有提供选项,或者给出了 -v 选项,则每个名称 指的是一个shell变量。 只读变量不能未设置。 如果指定了 -f,则每个名称都指一个 shell 函数,而 函数定义被删除。每个未设置的变量或函数都会从传递给后续命令的环境中删除。如果 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.
    【解决方案3】:

    根据手册页:

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

    如果您还没有导出变量,可以使用exec "$0" "$@" 重新启动您的shell,当然您也会丢失所有其他未导出的变量。似乎如果您在没有 exec 的情况下启动新的 shell,它会丢失该 shell 的只读属性。

    【讨论】:

    • 重启 shell 充其量是粗略的
    【解决方案4】:

    不,不在当前 shell 中。如果你想给它分配一个新的值,你将不得不派生一个新的外壳,它会有一个新的含义,不会被视为read only

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

    【讨论】:

      【解决方案5】:

      实际上,您可以取消设置只读变量。但我必须警告这是一种 hacky 方法。添加此答案,仅作为信息,不作为建议。需要您自担风险使用它。在 ubuntu 13.04、bash 4.2.45 上测试。

      此方法需要了解一些 bash 源代码,它继承自 this 答案。

      $ 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's answer 中提供的那样:

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

      sudo 根据内核的 ptrace_scope 设置可能需要也可能不需要。查看 vip9937 答案上的 cmets 了解更多详情。

      【讨论】:

      • 现在这就是我所说的乡巴佬 bash 编程;)
      • 注意:不要试图将cat &lt;&lt; EOF| sudo gdb 更改为sudo gdb &lt;&lt; EOF。它可能 起作用,因为重定向的输入提供程序 - bash 由于gdb 附件而被停止。
      • ^^ 标准输入的 EOF 和显式退出都会彻底退出 gdb。
      • 我喜欢一个班轮:echo -e "attach $$\n call unbind_variable(\"PI\")\n detach" | gdb
      • @SatyaMishra 这可以在一行中写得更简单!!见my commentmy answer
      【解决方案6】:

      我尝试了上面的 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
      

      【讨论】:

      【解决方案7】:

      专门针对 TMOUT 变量。如果 gdb 不可用,另一种选择是将 bash 复制到您的主目录并将二进制文件中的 TMOUT 字符串修补为其他内容,例如 XMOUX。然后再运行这层额外的shell,就不会超时了。

      【讨论】:

      • 比 gdb hack 还要邪恶。所以... +1!
      【解决方案8】:

      在zsh中,

      % typeset +r PI
      % unset PI
      

      (是的,我知道问题是 bash。但是当你用 Google 搜索 zsh 时,你也会得到一堆 bash 问题。)

      【讨论】:

      【解决方案9】:

      在 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() 的角度来看,你只需要考虑一下你的脚本。

      【讨论】:

        【解决方案10】:

        使用 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 一样危险...... ;-)

        【讨论】:

        • 我假设你在哪里说include ctypes.sh 你的意思是source ctypes.sh. ctypes.sh
        【解决方案11】:

        简短地说:灵感来自anishsane's answer

        编辑 2021-11-10:将 (int) 添加到 cast unbind_variable 结果。

        但语法更简单:

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

        作为一个功能进行了一些改进:

        我的destroy函数:

        如何使用可变元数据。注意罕见的 bashisms 的用法:local -n VARIABLE=$1${VARIABLE@a}...

        destroy () { 
            declare -p $1 &>/dev/null || return -1 # Return if variable not exist
            local -n variable=$1
            local reslne result flags=${variable@a}
            [ -z "$flags" ] || [ "${flags//*r*}" ] && { 
                unset $1    # Don't run gdb if variable is not readonly.
                return $?
            }
            while read -r resline; do
                [ "$resline" ] && [ -z "${resline%%\$1 = *}" ] &&
                    result=${resline##*1 = }
            done < <(
                exec gdb 2>&1 -ex 'call (int) 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 (int) unbind_variable("'$1'")' --pid=$$ --batch
        14      )
        15      return $result
        16  }
        
        • 第 2 行为提交的变量创建一个本地引用
        • 第 3 行防止在不存在的变量上运行
        • 第 4 行将参数的属性(元)存储到 $flags
        • 如果只读标志不存在,第 5 到 8 行将运行 unset 而不是 gdb
        • 第 9 到 12 行 while read ... result= ... donegdb 输出中获取 call (int) unbind_variable() 的返回码
        • 第 13 行 gdb 语法,使用 --pid--ex(参见 gdb --help)。
        • 第 15 行返回 $result of unbind_variable() 命令。

        使用中:

        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
        

        【讨论】:

          【解决方案12】:
          $ 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可以继承父级的变量,但不会继承它们的受保护状态。

          【讨论】:

          • 谢谢!这导致了一种禁用 TMOUT 的简单方法。编辑 ~/.ssh/config Host 部分,使其具有“RemoteCommand exec ${BASH}”。
          【解决方案13】:

          gdb 不可用时的替代方法:您可以使用the enable command 加载a custom builtin,这将使您取消设置只读属性。执行此操作的代码要点:

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

          显然,您应该将 TMOUT 替换为您关心的变量。

          如果您不想自己将其变成内置函数,我在 GitHub 中分叉了 bash,并添加了一个完全编写且可立即编译的可加载内置函数 readwrite。提交在https://github.com/josephcsible/bash/commit/bcec716f4ca958e9c55a976050947d2327bcc195。如果你想使用它,通过我的提交获取 Bash 源,运行 ./configure &amp;&amp; make loadables 来构建它,然后 enable -f examples/loadables/readwrite readwrite 将它添加到你正在运行的会话中,然后 readwrite TMOUT 来使用它。

          【讨论】:

            【解决方案14】:

            另一个没有GDBexternal binary 的解决方案(实际上是强调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
            

            【讨论】:

              【解决方案15】:
              $ 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 reference manual
              3. 请注意int 的演员表。否则,将导致以下错误:'unbind_variable' has unknown return type; cast the call to its declared return typebash source code表示unbind_variable函数的返回类型是int
              4. 此答案与an answer over at superuser.com 基本相同。我将演员添加到 int 以克服 unknown return type 错误。

              【讨论】:

                猜你喜欢
                • 2014-09-03
                • 2015-03-14
                • 2015-03-08
                • 2011-03-12
                • 1970-01-01
                • 2014-05-25
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多