【问题标题】:Declaring global variable inside a function在函数内部声明全局变量
【发布时间】:2012-03-26 11:42:39
【问题描述】:

我想做的是跟随。在函数内部,我需要为一个变量赋值,该变量的名称取自另一个变量。换句话说:

func() {  
  #  
  # Here happens something that ultimately makes $arg="var_name"
  # 
  declare -g ${arg}=5
}

func

echo ${var_name}; # Prints out "5"

上面的代码 sn-p 在 bash 4.2 中运行良好。但是,在 4.2 之前的 bash 中,declare 没有 -g 选项。我在 google 上找到的所有内容都表明要在函数中定义全局变量,我应该只使用 var=value 语法,但不幸的是 var 本身依赖于另一个变量。 ${arg}=5 也不起作用。 (上面写着-bash: var_name=5: command not found

出于好奇,这一切的原因是该函数实际上是从脚本参数创建全局变量,即运行script --arg1=val 会自动创建名为arg1 的变量,其值为val。节省大量样板代码。

【问题讨论】:

  • 您可以将 var=value 构造为字符串并使用 bash 内置命令 eval 对其进行评估。
  • @minopret 你能重新发表你的评论作为答案吗?除非有人想出一个不那么骇人听闻的解决方案,否则我只会接受你的回答,它绝对有效。
  • > 运行 script --arg1=val 会自动创建名为 arg1 的变量,其值为 val ermmm...你不能像 arg1=val script 那样运行它,它会在脚本运行期间设置吗?

标签: bash shell scope


【解决方案1】:

declare 在函数内无法按预期工作。 我需要在函数中声明的只读全局变量。 我在一个函数中尝试过,但没有成功:

declare -r MY_VAR=1

但这没有用。使用readonly 命令做了:

func() {
    readonly MY_VAR=1
}
func
echo $MY_VAR
MY_VAR=2

这将打印 1 并为第二次赋值给出错误“MY_VAR: readonly variable”。

【讨论】:

    【解决方案2】:

    由于它被标记为shell,重要的是要注意declare 是仅在有限的shell 中有效的关键字,所以它是否支持-g 是没有实际意义的。要在通用 Bourne shell 中执行此类操作,您只需使用 eval:

    eval ${arg}=5
    

    【讨论】:

      【解决方案3】:

      我认为您需要带有 -v 开关的 printf 内置函数:

      func() {
          printf -v "$var" '5'
      }
      var=var_name
      func
      echo "$var_name"
      

      将输出5

      【讨论】:

        【解决方案4】:

        使用“eval”(或任何直接赋值)会使您对特殊字符(或值注入等问题)感到头疼。

        仅使用“阅读”就取得了巨大的成功

        $ function assign_global() {
        >   local arg="$1"
        >   IFS="" read -d "" $arg <<<"$2"
        >}
        $ assign_global MYVAR 23
        echo $MYVAR
        23
        $ assign_global MYVAR "\"'"
        "'
        

        【讨论】:

        • 但这会在变量后面附加一个尾随换行符……
        • @gniourf_gniourf - 给定的示例确实会附加换行符,但该示例专门针对正在读取的字符串中的特殊字符(例如换行符)。如果您确定正在读取的参数中没有换行符等,您可以从 read 中删除 -d 选项以读取直到但不包括第一个换行符。
        • 我认为这会起作用:read -d"\0" $arg &lt;&lt;&lt; "$2",无需更改IFS
        【解决方案5】:

        您可以将 var=value 构造为字符串并使用 bash 内置命令 eval 对其进行评估。

        【讨论】:

        • @sivann 看看我的回答
        • eval var=\"value with a space\"如果需要使用引号,必须转义
        【解决方案6】:

        如果您想要一些不那么骇人听闻的东西,请尝试使用export 代替declare -g。它现在具有成为环境变量的额外好处。

        func() {  
        #  
        # Here happens something that ultimately makes $arg="var_name"
        # 
        export ${arg}=5
        }
        
        func
        
        echo ${var_name}; # Prints out "5"
        

        不幸的是,这仍然不适用于数组。

        【讨论】:

        • 使用数组导出不起作用,你可以看看我的回答
        【解决方案7】:

        在 Bash 中用于在函数定义中声明数组变量,您可以将 local 命令与 eval 命令结合使用,如下例所示:

        #!/bin/bash
        function test
        {
            local -g $1
            local array=( AA BB 'C C' DD)
            eval ${1}='("${array[@]}")'
        }
        test VAR
        echo VAR=${VAR[@]:1:2}
        

        输出将是:

        $./test.sh
        VAR=BB C C
        

        适用于:GNU bash,版本 4.4.18

        【讨论】:

        • 也适用于GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin18)
        【解决方案8】:

        localdeclare 内置命令的存在可能会让人反直觉地认为,所需的功能实际上可以从 export 内置命令中获得,至少在 Bash 上是这样。

        根据 Bash 手册,export 命令有一个 -n 选项,“导致从每个名称中删除导出属性”。这也适用于在前面声明具有值的变量。

        下面的sn-p演示了用法:

        VAR=old-value
        function func () {
            export -n "$1=$2"
        }
        func VAR new-value
        echo "VAR=$VAR"     # prints VAR=new-value
        

        可以创建自己的global 函数,并在其他函数中使用它:

        VAR=old-value
        function global () {
            export -n "$1=$2"
        }
        function main () {
            global VAR "$1"
        }
        main main-value
        echo "VAR=$VAR"            # prints VAR=main-value
        

        这是经过验证的工作

        • 在(现代)OS/390 z/OS 上运行的旧 Bash 2.03.0(1)
        • 在 macOS 10.15 上的 Bash 3.2.57(1) 上
        • 在 CentOS 6 上的 Bash 4.1.2(2) 上

        我用它来检查各种可能性:

        #!env bash
        
        export LANG=C LC_ALL=C
        
        A=a0
        B=b0
        C=c0
        D=d0
        E=e0
        export F=f0
        
        function testfunc () {
            local "$1=$2"       ; shift 2
            declare "$1=$2"     ; shift 2
            declare -g "$1=$2"  ; shift 2
            export -n "$1=$2"   ; shift 2
            export "$1=$2"
        }
        
        echo "$A/$B/$C/$D/$E"
        testfunc A a1 B b1 C c1 D d1 E e1
        echo "$A/$B/$C/$D/$E"
        
        export -p | grep -E '^declare -x [ABCDEF]='
        

        在 macOS 10.15 的情况下,这显示类似于以下的输出:

        $ bash test.sh
        a0/b0/c0/d0/e0
        test.sh: line 15: declare: -g: invalid option
        declare: usage: declare [-afFirtx] [-p] [name[=value] ...]
        a0/b0/c0/d1/e1
        declare -x E="e1"
        declare -x F="f0"
        

        输出显示testfunc 函数中使用的localdeclare 命令实际上不会在全局上下文中应用变量(如文档所述),并且declare -g 选项尚未得到广泛支持。它还显示变量 D 和 E 在全局上下文中已从 testfunc 函数内部更改(如所希望的那样),而只有 E 变量已被 export 命令标记为导出为副作用,表明export -n 选项有效。变量 F 只是为了验证 grep 命令是否按预期工作。

        【讨论】:

          【解决方案9】:

          Eval 将与 Array 变量一起使用...我只需要在 eval 语句中单引号引用本地 Array 变量。

          以下代码一次读取文件(第一个参数)一行,然后将其复制到传递给 get_file 的第二个参数的变量名。

          get_file () {
            declare -a Array=();
            while read line; do
              Array=("${Array[@]}" "($line)")
            done < ${1}
            eval ${2}='("${Array[@]}")'
            unset Array
          }
          
          declare -a my_array=();
          get_file /etc/myfile.conf my_array
          echo ${#my_array[@]}

          【讨论】:

          • 您是否尝试实现 mapfile 内置函数? mapfile -t my_array &lt; /etc/myfile.conf 很乐意为您做到这一点,而且效率更高。
          • 不幸的是,这是针对 Solaris 10 应用程序的,该应用程序似乎没有在该 bash 实现中包含 mapfile。所以这就是kludge
          • mapfile 是内置的 Bash,因此与操作系统无关。不过,Bash 版本是相关的。 mapfile 出现在第 4 版中(已经有几年的历史了)。
          • 这就解释了。我用来测试的系统上的 bash 版本是未打补丁的 3.2 版。
          【解决方案10】:

          也许您可以使用以下函数在 bash( 2 个参数,则该值将是数组类型。 E.G

          _set2globals variable_name arg1 [arg2] [arg3] [...]
          

          来源:

          # dynamically set $variable_name($1)=$values($2...) to globals scope
          function _set2globals()
          {
              if (( $# < 2 )); then
                  printf "$FUNCNAME: expect at least 2 argument, but %d you given.\n" $# >&2
                  exit 1
              fi
              local ___pattern_='^[_a-zA-Z][_0-9a-zA-Z]*$'
              if [[ ! $1 =~ $___pattern_ ]]; then
                  printf "$FUNCNAME: invalid variable name: %s.\n" "$1" >&2
                  exit 1
              fi
              local __variable__name__=$1
              shift
              local ___v_
              local ___values_=()
              while (($# > 0)); do
                  ___v_=\'${1//"'"/"'\''"}\'
                  ___values_=("${___values_[@]}" "$___v_") # push to array
                  shift
              done
          
              eval $__variable__name__=\("${___values_[@]}"\)
          }
          

          【讨论】:

            猜你喜欢
            • 2014-01-17
            • 2012-05-23
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-09-01
            • 1970-01-01
            • 1970-01-01
            • 2011-10-11
            相关资源
            最近更新 更多