【问题标题】:How to list variables declared in script in bash?如何列出bash脚本中声明的变量?
【发布时间】:2010-11-21 06:31:57
【问题描述】:

在我的 bash 脚本中,有很多变量,我必须做一些事情来将它们保存到文件中。 我的问题是如何列出我的脚本中声明的所有变量并得到这样的列表:

VARIABLE1=abc
VARIABLE2=def
VARIABLE3=ghi

【问题讨论】:

    标签: bash variables


    【解决方案1】:

    set 会输出变量,不幸的是它也会输出函数定义。

    幸运的是 POSIX 模式只输出变量:

    ( set -o posix ; set ) | less
    

    管道到less,或重定向到您想要选项的位置。

    所以要获取仅在脚本中声明的变量:

    ( set -o posix ; set ) >/tmp/variables.before
    source script
    ( set -o posix ; set ) >/tmp/variables.after
    diff /tmp/variables.before /tmp/variables.after
    rm /tmp/variables.before /tmp/variables.after
    

    (或者至少是基于此的东西:-))

    【讨论】:

    • 不使用临时文件:VARS="`set -o posix ; set`"; source script; SCRIPT_VARS="`grep -vFe "$VARS" <<<"$(set -o posix ; set)" | grep -v ^VARS=`"; unset VARS;。这也将以可保存的格式输出变量。该列表将包括脚本更改的变量(这取决于是否需要)
    • @ErikAronesty 如果您希望能够使用source 重新创建环境,您应该能够使用declare -p 的输出来实现。
    • 如果您想在不使用临时文件的情况下使用 diff(或任何类似命令)比较之前和之后的环境,可以使用以下命令:before=$(set -o posix; set); dosomestuff; diff <(echo "$before") <(set -o posix; set)
    • @Caesar 不,我只尝试了非空数组。在空数组的情况下你是对的——它们没有被打印出来。
    • 对于任何其他 zsh 用户:posix 选项在 zsh (set: no such option: posix) 中不起作用 - 我们必须改为在 /bin/bash 中执行命令。
    【解决方案2】:
    compgen -v
    

    它列出了所有变量,包括本地变量。 是从Get list of variables whose name matches a certain pattern学来的,在my script使用过。

    【讨论】:

    • 遗憾的是,compgen -v 还列出了本地未设置的全局变量。不确定这是长期存在的错误还是期望的行为。
    【解决方案3】:
    for i in _ {a..z} {A..Z}; do eval "echo \${!$i@}" ; done | xargs printf "%s\n"
    

    这必须打印所有 shell 变量名称。您可以在获取文件之前和之后获取一个列表,就像使用“set”来区分哪些变量是新的(如其他答案中所述)。但请记住,使用 diff 进行此类过滤可以过滤掉一些您需要但在获取文件之前就已存在的变量。

    在您的情况下,如果您知道变量的名称以“VARIABLE”开头,那么您可以获取脚本并执行以下操作:

    for var in ${!VARIABLE@}; do
       printf "%s%q\n" "$var=" "${!var}"
    done
    

    更新:对于纯 BASH 解决方案(不使用外部命令):

    for i in _ {a..z} {A..Z}; do
       for var in `eval echo "\\${!$i@}"`; do
          echo $var
          # you can test if $var matches some criteria and put it in the file or ignore
       done 
    done
    

    【讨论】:

    • +1 和 oneliner 版本(带有单个 eval):eval "printf '%q\n' $(printf ' "${!%s@}"' _ {a..z} {A..Z})"
    • 哇!这是什么黑魔法"\\${!$i@}"?!我看到像echo "${!BASH_C@}" 这样简单的东西会转储所有以BASH_C 开头的变量,但是为什么呢? ! 是间接运算符,但间接超过什么?我们最终列出了变量,而不是它们的值。到底是怎么回事! :)
    • 啊,我应该读到tldp.org/LDP/abs/html/parameter-substitution.html 的末尾,因为它就是在此处描述的......
    【解决方案4】:

    根据上面的一些答案,这对我有用:

    before=$(set -o posix; set | sort);
    

    源文件

    comm -13 <(printf %s "$before") <(set -o posix; set | sort | uniq) 
    

    【讨论】:

      【解决方案5】:

      如果您可以进行后处理(如前所述),您可能只需在脚本的开头和结尾放置一个 set 调用(每个到不同的文件),然后对两个文件进行比较。意识到这仍然会包含一些噪音。

      您也可以以编程方式执行此操作。要将输出限制在您当前的范围内,您必须实现一个包装器来创建变量。例如

      store() {
          export ${1}="${*:2}"
          [[ ${STORED} =~ "(^| )${1}($| )" ]] || STORED="${STORED} ${1}"
      }
      
      store VAR1 abc
      store VAR2 bcd
      store VAR3 cde
      
      for i in ${STORED}; do
          echo "${i}=${!i}"
      done
      

      产量

      VAR1=abc
      VAR2=bcd
      VAR3=cde
      

      【讨论】:

        【解决方案6】:

        这是类似于@GinkgoFr 的答案,但没有@Tino 或@DejayClayton 确定的问题, 并且比@DouglasLeeder 聪明的set -o posix bit 更健壮:

        + function SOLUTION() { (set +o posix; set) | sed -ne '/^\w\+=/!q; p;'; }
        

        不同之处在于此解决方案在第一个非变量报告后停止,例如set报告的第一个函数

        顺便说一句:“蒂诺”问题已解决。即使 POSIX 已关闭且函数由 set 报告, 解决方案的sed ... 部分仅允许通过变量报告(例如VAR=VALUE 行)。 特别是,A2 确实没有虚假地将其放入输出中。

        + function a() { echo $'\nA2=B'; }; A0=000; A9=999; 
        + SOLUTION | grep '^A[0-9]='
        A0=000
        A9=999
        

        并且:“DejayClayton”问题已解决(在变量值中嵌入换行符不会中断输出 - 每个 VAR=VALUE 得到一个输出行):

        + A1=$'111\nA2=222'; A0=000; A9=999; 
        + SOLUTION | grep '^A[0-9]='
        A0=000
        A1=$'111\nA2=222'
        A9=999
        

        注意:@DouglasLeeder 提供的解决方案存在“DejayClayton”问题(带有嵌入换行符的值)。 下面,A1 是错误的,A2 根本不应该显示。

        $ A1=$'111\nA2=222'; A0=000; A9=999; (set -o posix; set) | grep '^A[0-9]='
        A0=000
        A1='111
        A2=222'
        A9=999
        

        最后:我不认为bash 的版本很重要,但它可能。我对此进行了测试/开发:

        $ bash --version
        GNU bash, version 4.4.12(1)-release (x86_64-pc-msys)
        

        POST-SCRIPT:鉴于对 OP 的其他一些响应,我可以确定 set 总是 将值内的换行符转换为 \n,此解决方案依靠避免“DejayClayton”问题。也许这是一种现代行为?还是编译时变体?还是set -oshopt 选项设置?如果知道此类变化,请添加评论...

        【讨论】:

          【解决方案7】:

          聚会有点晚了,但这里有另一个建议:

          #!/bin/bash
          
          set_before=$( set -o posix; set | sed -e '/^_=*/d' )
          
          # create/set some variables
          VARIABLE1=a
          VARIABLE2=b
          VARIABLE3=c
          
          set_after=$( set -o posix; unset set_before; set | sed -e '/^_=/d' )
          diff  <(echo "$set_before") <(echo "$set_after") | sed -e 's/^> //' -e '/^[[:digit:]].*/d'
          

          diff+sed 管道命令行以所需格式输出所有脚本定义的变量(在 OP 的帖子中指定):

          VARIABLE1=a
          VARIABLE2=b
          VARIABLE3=c
          

          【讨论】:

            【解决方案8】:

            如果您只关心打印具有静态值的变量列表(即扩展在这种情况下不起作用),那么另一种选择是在文件中添加开始和结束标记,告诉您块的位置静态变量定义是,例如

            #!/bin/bash
            
            # some code
            
            # region variables
            VAR1=FOO
            VAR2=BAR
            # endregion
            
            # more code
            

            然后你就可以打印文件的那部分了。

            这是我为此准备的:

            function show_configuration() {
               local START_LINE=$(( $(< "$0" grep -m 1 -n "region variables" | cut -d: -f1) + 1 ))
               local END_LINE=$(( $(< "$0" grep -m 1 -n "endregion" | cut -d: -f1) - 1 ))
               < "$0" awk "${START_LINE} <= NR && NR <= ${END_LINE}"
            }
            
            

            首先,请注意变量块驻留在此函数所在的同一文件中,因此我可以使用$0 访问文件的内容。

            我使用“区域”标记来分隔不同的代码区域。所以我简单地将grep 用于“变量”区域标记(第一个匹配:grep -m 1)并让grep 为行号添加前缀(grep -n)。然后我必须从匹配输出中删除行号(拆分:)。最后,添加或减去 1,因为我不希望标记成为输出的一部分。

            现在,要打印该文件范围,我使用 awk 和行号条件。

            【讨论】:

              【解决方案9】:

              尝试使用脚本(我们称之为“ls_vars”):

                #!/bin/bash
                set -a
                env > /tmp/a
                source $1
                env > /tmp/b
                diff /tmp/{a,b} | sed -ne 's/^> //p'
              

              chmod +x 它,然后:

                ls_vars your-script.sh > vars.files.save
              

              【讨论】:

                【解决方案10】:

                从安全角度来看,@akostadinov 的answer 或@JuvenXu 的answer 都比依赖set 命令的非结构化输出更可取,因为存在以下潜在的安全漏洞:

                #!/bin/bash
                
                function doLogic()
                {
                    local COMMAND="${1}"
                    if ( set -o posix; set | grep -q '^PS1=' )
                    then
                        echo 'Script is interactive'
                    else
                        echo 'Script is NOT interactive'
                    fi
                }
                
                doLogic 'hello'   # Script is NOT interactive
                doLogic $'\nPS1=' # Script is interactive
                

                上面的函数doLogic 使用set 来检查变量PS1 的存在以确定脚本是否是交互式的(不管这是否是实现该目标的最佳方式;这只是一个例子。)

                但是,set 的输出是非结构化的,这意味着任何包含换行符的变量都可能完全污染结果。

                这当然是潜在的安全风险。相反,请使用 Bash 对间接变量名扩展的支持,或 compgen -v

                【讨论】:

                  【解决方案11】:

                  试试这个:set | egrep "^\w+="(有或没有| less 管道)

                  第一个建议的解决方案( set -o posix ; set ) | less 有效,但有一个缺点:它将控制代码传输到终端,因此无法正确显示。例如,如果有 (likely) 一个 IFS=$' \t\n' 变量,我们可以看到:

                  IFS='
                  '
                  

                  …而不是。

                  我的egrep 解决方案正确地显示了这个(以及最终其他类似的)

                  【讨论】:

                  • 8年了,你知道的
                  • 投反对票,因为它完全错误 bash -c $'a() { echo "\nA=B"; }; unset A; set | egrep "^\w+="' | grep ^A 显示 A=B" -> 失败!
                  • 奇怪的测试似乎只暗示了“_”(上一个命令的最后一个参数)伪变量,但在其他人(尤其是 IFS)上不会失败。在“bash -c”下运行它也可能使它不重要。你至少应该保持中立而不是投反对票。
                  【解决方案12】:

                  我可能在不久前偷了the answer ...无论如何作为一个功能略有不同:

                      ##
                      # usage source bin/nps-bash-util-funcs
                      # doEchoVars
                      doEchoVars(){
                  
                          # if the tmp dir does not exist
                          test -z ${tmp_dir} && \
                          export tmp_dir="$(cd "$(dirname $0)/../../.."; pwd)""/dat/log/.tmp.$$" && \
                          mkdir -p "$tmp_dir" && \
                          ( set -o posix ; set )| sort >"$tmp_dir/.vars.before"
                  
                  
                          ( set -o posix ; set ) | sort >"$tmp_dir/.vars.after"
                          cmd="$(comm -3 $tmp_dir/.vars.before $tmp_dir/.vars.after | perl -ne 's#\s+##g;print "\n $_ "' )"
                          echo -e "$cmd"
                      } 
                  

                  【讨论】:

                    【解决方案13】:

                    printenv 命令:

                    printenv 打印所有 environment variables 及其值。

                    祝你好运……

                    【讨论】:

                    • OP 想要脚本变量而不是环境变量。如果您希望 printenv 捕获它们,则必须导出脚本变量。
                    【解决方案14】:

                    执行此操作的简单方法是在运行脚本之前通过设置系统环境变量来使用bash strict mode,并使用 diff 仅对脚本中的那些进行排序:

                    # Add this line at the top of your script :
                    set > /tmp/old_vars.log
                    
                    # Add this line at the end of your script :
                    set > /tmp/new_vars.log
                    
                    # Alternatively you can remove unwanted variables with grep (e.g., passwords) :
                    set | grep -v "PASSWORD1=\|PASSWORD2=\|PASSWORD3=" > /tmp/new_vars.log
                    
                    # Now you can compare to sort variables of your script :
                    diff /tmp/old_vars.log /tmp/new_vars.log | grep "^>" > /tmp/script_vars.log
                    

                    您现在可以在 /tmp/script_vars.log 中检索脚本的变量。 或者至少是基于此的东西!

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 2020-08-14
                      • 1970-01-01
                      • 2020-01-20
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2013-03-27
                      • 1970-01-01
                      相关资源
                      最近更新 更多