【问题标题】:Can ${var} parameter expansion expressions be nested in bash?${var} 参数扩展表达式可以嵌套在bash中吗?
【发布时间】:2018-10-20 23:28:27
【问题描述】:

我拥有的是这样的:

progname=${0%.*}
progname=${progname##*/}

这可以嵌套(或不)成一行,即单个表达式吗?

我正在尝试从脚本名称中去除路径和扩展名,以便只留下基本名称。以上两行工作正常。我的“C”天性只是驱使我更加混淆这些。

【问题讨论】:

    标签: bash


    【解决方案1】:

    这种嵌套在 bash 中似乎是不可能的,但它在 zsh 中有效:

    progname=${${0%.*}##*/}
    

    【讨论】:

      【解决方案2】:

      如果用嵌套,你的意思是这样的:

      #!/bin/bash
      
      export HELLO="HELLO"
      export HELLOWORLD="Hello, world!"
      
      echo ${${HELLO}WORLD}

      那么不,你不能嵌套${var} 表达式。 bash 语法扩展器不会理解它。

      但是,如果我正确理解您的问题,您可能会考虑使用 basename 命令 - 它从给定文件名中删除路径,如果给定扩展名,也将删除它。例如,运行basename /some/path/to/script.sh .sh 将返回script

      【讨论】:

      • 如果您使用索引,它们可以,对吗?例如,ARR=('foo' 'bar' 'bogus'); i=0; while /bin/true ; do echo ${ARR[$i]} ; i=(( (i + 1) % 3 )); done 显然作为代码没有用,但可以作为示例使用。
      • 其实这个在某些情况下被支持的,至少在bashksh。这有效:x=yyy; y=xxxyyy; echo ${y%${x}}。我认为重要的是嵌套扩展是其中一个运算符的参数。不过,我还没有看到它的文档记录得很好。
      • 这对我不起作用,我得到 bash: ${${HELLO}WORLD}: bad replacement。有任何想法吗?我在使用 Fedora。
      • @CarlosBribiescas,正如@Tim 所说,“不,你不能嵌套表达式”。示例echo ${${HELLO}WORLD} 说明了 不可能。
      • 代替间接扩展,你可以使用nameref来构建变量名! See my answer
      【解决方案3】:

      基本名称 bultin 可以帮助解决这个问题,因为您专门将 / 拆分为一个部分:

      user@host# var=/path/to/file.extension
      user@host# basename ${var%%.*}
      file
      user@host#
      

      它并不比两行变体快,但它只是使用内置功能的一行。或者,使用可以做模式嵌套的 zsh/ksh。 :)

      【讨论】:

      • 抱歉格式化。就试一试吧。 :) var=$( 基本名称 ${var%%.*} )
      • 或使用 bash 特定的基本扩展名删除,正如 Tim 所说,我错过了......:D
      【解决方案4】:

      我知道这是一条古老的线,但这是我的 2 美分。

      这是一个(公认的笨拙的)bash 函数,它允许所需的功能:

      read_var() {
        set | grep ^$1\\b | sed s/^$1=//
      }
      

      这是一个简短的测试脚本:

      #!/bin/bash
      
      read_var() {
        set | grep ^$1\\b | sed s/^$1=//
      }
      
      FOO=12
      BAR=34
      
      ABC_VAR=FOO
      DEF_VAR=BAR
      
      for a in ABC DEF; do
        echo $a = $(read_var $(read_var ${a}_VAR))
      done
      

      输出如预期:

      ABC = 12
      DEF = 34
      

      【讨论】:

      • 我知道这已经很老了,但是您可以取消 grep 并使用 sed -n p 作为:set | sed -n s/^$1=//p。顺便说一句,不错的 hack。
      【解决方案5】:

      Bash 支持间接扩展:

      $ FOO_BAR="foobar"
      $ foo=FOO
      $ foobar=${foo}_BAR
      $ echo ${foobar}
      FOO_BAR
      $ echo ${!foobar}
      foobar
      

      这应该支持您正在寻找的嵌套。

      【讨论】:

      • 您好!意味着在外壳中?这里的意思是用 foobar 的值替换?
      • 很好的例子。如果他们没有可用的间接参数扩展,在这种情况下会怎么做?
      • 效果很好,谢谢! :) 不幸的是,我犯了一个小错误,让我质疑这个事实并用谷歌搜索了一个小时,哈哈。
      【解决方案6】:

      实际上可以在 bash 中创建嵌套变量,使用两个步骤。

      这是一个基于 Tim 的帖子的测试脚本,使用了 user1956358 建议的想法。

      #!/bin/bash
      export HELLO="HELLO"
      export HELLOWORLD="Hello, world!"
      
      # This command does not work properly in bash
      echo ${${HELLO}WORLD}
      
      # However, a two-step process does work
      export TEMP=${HELLO}WORLD
      echo ${!TEMP}
      

      输出是:

      Hello, world!
      

      通过从命令行运行“info bash”,然后搜索“Shell Parameter Expansion”,可以解释许多巧妙的技巧。我自己今天也读了几本,我一天只浪费了大约 20 分钟,但我的剧本会变得更好......

      更新:在阅读更多内容后,我根据您最初的问题建议此替代方案。

      progname=${0##*/}
      

      返回

      bash
      

      【讨论】:

      • OP 询问是否可以在 bash 中进行嵌套扩展,基本上你回答(这是正确的):不,你需要将表达式一个扩展为一个临时变量,然后使用第二个表达式那个变量。但是,其他 shell 支持模式嵌套。
      【解决方案7】:

      ${${a}} 之类的表达式不起作用。要解决它,您可以使用eval

      b=value
      a=b
      eval aval=\$$a
      echo $aval
      

      输出是

      value

      【讨论】:

        【解决方案8】:

        如果动机是本着 Python 的“理解”精神“混淆”(我会说简化)数组处理,请创建一个按顺序执行操作的辅助函数。

        function fixupnames()
           {
           pre=$1 ; suf=$2 ; shift ; shift ; args=($@)
           args=(${args[@]/#/${pre}-})
           args=(${args[@]/%/-${suf}})
           echo ${args[@]}
           }
        

        您可以将结果与漂亮的单线一起使用。

        $ echo $(fixupnames a b abc def ghi)
        a-abc-b a-def-b a-ghi-b
        

        【讨论】:

          【解决方案9】:

          一个旧线程,但也许答案是使用 Indirection:${!PARAMETER}

          例如,考虑以下几行:

          H="abc"
          PARAM="H"
          echo ${!PARAM} #gives abc
          

          【讨论】:

            【解决方案10】:

            以下选项对我有用:

            NAME="par1-par2-par3"
            echo $(TMP=${NAME%-*};echo ${TMP##*-})
            

            输出是:

            par2
            

            【讨论】:

            • 这是最好的答案
            • 太棒了。我只是想知道,为什么没有第二个(内部)echo 它将par2 视为命令?
            • 这将创建一个子外壳。参数扩展本身不会创建子shell。
            【解决方案11】:

            eval 将允许你做你想做的事:

            export HELLO="HELLO"
            export HELLOWORLD="Hello, world!"
            
            eval echo "\${${HELLO}WORLD}"
            

            输出:Hello, world

            【讨论】:

              【解决方案12】:

              OP 的原始问题有一个 1 行的解决方案,即删除了文件扩展名的脚本的基本名称:

              progname=$(tmp=${0%.*} ; echo ${tmp##*/})
              

              这是另一个,但是,使用了 basename 的作弊:

              progname=$(basename ${0%.*})
              

              其他答案已经偏离了 OP 的原始问题,而是专注于是否可以使用 ${!var} 扩展表达式的结果,但遇到了 var 必须明确匹配变量名的限制。话虽如此,如果您将表达式与分号链接在一起,则没有什么可以阻止您获得 1-liner 答案。

              ANIMAL=CAT
              BABYCAT=KITTEN
              tmp=BABY${ANIMAL} ; ANSWER=${!tmp} # ANSWER=KITTEN
              

              如果你想让它看起来像一个单一的语句,你可以将它嵌套在一个子shell中,即

              ANSWER=$( tmp=BABY${ANIMAL) ; echo ${!tmp} ) # ANSWER=KITTEN
              

              一个有趣的用法是间接作用于 bash 函数的参数。然后,您可以嵌套 bash 函数调用以实现多级嵌套间接,因为我们可以执行嵌套命令:

              这是一个表达式的间接演示:

              deref() { echo ${!1} ; }
              ANIMAL=CAT
              BABYCAT=KITTEN
              deref BABY${ANIMAL} # Outputs: KITTEN
              

              这是通过嵌套命令进行多级间接的演示:

              deref() { echo ${!1} ; }
              export AA=BB
              export BB=CC
              export CC=Hiya
              deref AA # Outputs: BB
              deref $(deref AA) # Outputs: CC
              deref $(deref $(deref AA)) # Outputs: Hiya
              

              【讨论】:

              • 感谢间接解释!确实有点可惜,它只能用来引用已有的变量名。当然可以制作一个单行器,但归根结底,我试图避免涉及临时变量的分配。
              • @steven-lu 我的deref() 函数呢?如果你有这个声明,你可以让你的 oneliner 没有临时变量?
              • 从任何合理的衡量标准来看,这对于维护来说比使用临时变量更容易混淆 ? 让我想起了 C 预处理器的滥用
              【解决方案13】:

              虽然这是一个非常古老的线程,但该设备非常适合直接或随机选择文件/目录进行处理(播放音乐、选择要观看的电影或要阅读的书等)。

              在 bash 中,我认为通常不能直接嵌套任何两个相同类型的扩展是正确的,但如果你可以用某种不同类型的扩展将它们分开,就可以做到。

              e=($(find . -maxdepth 1 -type d))
              c=${2:-${e[$((RANDOM%${#e[@]}))]}}
              

              说明:e是目录名数组,c是选定目录,或者显式命名为$2,

              ${2:-...}  
              

              其中 ... 是由

              给出的替代随机选择
              ${e[$((RANDOM%${#e[@]}))]}  
              

              在哪里

              $((RANDOM%...))  
              

              bash 生成的数字除以数组 e 中的项目数,由

              给出
              ${#e[@]}  
              

              产生成为数组 e 索引的余数(来自 % 运算符)

              ${e[...]}
              

              因此您有四个嵌套扩展。

              【讨论】:

                【解决方案14】:

                如果您按照下面显示的方式进行中间步骤,它将起作用:

                export HELLO="HELLO"
                export HELLOWORLD="Hello, world!"
                
                varname=${HELLO}WORLD
                echo ${!varname}
                

                【讨论】:

                • 但不被其他 shell 共享。不是我想要的,而是接近的东西。
                【解决方案15】:

                由于那里已经有很多答案,我只想介绍两种两种不同的方法:嵌套参数扩展 变量名操作。 (所以你会在那里找到四个不同的答案:)。

                参数扩展不是真正嵌套,而是一行完成:

                没有分号 (;) 也没有换行符:

                progname=${0%.*} progname=${progname##*/}
                

                另一种方式:您可以使用 fork 到 basename

                progname=$(basename ${0%.*})
                

                这将完成这项工作。

                关于连接变量名

                如果你想构造变量名,你可以

                使用间接扩展

                foobar="baz"
                varname="foo"
                varname+="bar"
                echo ${!varname}
                baz
                

                或使用 nameref

                foobar="baz"
                bar="foo"
                declare -n reffoobar=${bar}bar
                echo $reffoobar
                baz
                

                【讨论】:

                • 哇。我可以看到 nameref 给我带来了很多麻烦。就因为它在那里,我应该使用它吗?我会在一周内记住这样的默默无闻吗?
                猜你喜欢
                • 1970-01-01
                • 2022-01-04
                • 2020-09-16
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多