【问题标题】:Parse out key=value pairs into variables将 key=value 对解析为变量
【发布时间】:2015-05-20 11:07:28
【问题描述】:

我有一堆不同类型的文件需要定期查看,它们的共同点是这些行都有一堆key=value 类型的字符串。所以像:

Version=2 Len=17 Hello Var=Howdy Other

我希望能够直接从 awk 中引用这些名称...所以类似于:

cat some_file | ... | awk '{print Var, $5}' # prints Howdy Other

我该怎么做呢?

【问题讨论】:

  • 您想使用 awk 有什么特别的原因吗?关联数组(一些其他语言称为映射或哈希)也可以在原生 bash 中使用。
  • 另外,您能否更明确地解释一下解析规则?我希望上面分配Len=17 Hello - 将Hello 单词与Len 的值分组 - 因为Var=Howdy Other 的给定行为依赖于隐式分组所有不包含= 符号的单词作为扩展上一个单词的内容。
  • 在问题的输出中,VarHowdyOther 来自 $5。或者我读了它。
  • @CharlesDuffy 值不会有空格。所以Len=17 是一个设置该变量的词,Hello 是另一个不设置任何内容的词。
  • @CharlesDuffy 你会如何在 bash 中做到这一点?

标签: bash awk


【解决方案1】:

你能得到的最接近的方法是将变量解析成一个关联数组,每行第一件事。也就是说,

awk '{ delete vars; for(i = 1; i <= NF; ++i) { n = index($i, "="); if(n) { vars[substr($i, 1, n - 1)] = substr($i, n + 1) } } Var = vars["Var"] } { print Var, $5 }'

更具可读性:

{
  delete vars;                   # clean up previous variable values
  for(i = 1; i <= NF; ++i) {     # walk through fields
    n = index($i, "=");          # search for =
    if(n) {                      # if there is one:

                                 # remember value by name. The reason I use
                                 # substr over split is the possibility of
                                 # something like Var=foo=bar=baz (that will
                                 # be parsed into a variable Var with the
                                 # value "foo=bar=baz" this way).
      vars[substr($i, 1, n - 1)] = substr($i, n + 1)
    }
  }

  # if you know precisely what variable names you expect to get, you can
  # assign to them here:
  Var     = vars["Var"]
  Version = vars["Version"]
  Len     = vars["Len"]
}
{
  print Var, $5                  # then use them in the rest of the code
}

【讨论】:

  • 不错。我猜你希望for(i = 1; i &lt;= NF; ++i) 中的&lt;= 也处理最后一个键值对。此外,没有任何临时变量的print vars["Var"] 也可以。
  • @Arjan 当然,&lt;= NF 是对的;谢谢。我在答案中修复了它。如果您直接使用vars[foo],那么您也可以在没有VarVersionLen 变量的情况下工作;他们只是为了使问题中的代码正常工作。
【解决方案2】:
$ cat file | sed -r 's/[[:alnum:]]+=/\n&/g' | awk -F= '$1=="Var"{print $2}'
Howdy Other

或者,避免 cat 的无用使用:

$ sed -r 's/[[:alnum:]]+=/\n&/g' file | awk -F= '$1=="Var"{print $2}'
Howdy Other

工作原理

  • sed -r 's/[[:alnum:]]+=/\n&amp;/g'

    这会将每个键值对放在自己的行上。

  • awk -F= '$1=="Var"{print $2}'

    这会读取键值对。由于选择的字段分隔符为=,因此键以字段1 结尾,值以字段2 结尾。因此,我们只需查找第一个字段为Var 的行并打印相应的值。

【讨论】:

    【解决方案3】:

    我将尝试向您解释一种非常通用的方法来执行此操作,如果您想打印其他内容,您可以轻松适应。

    假设您有一个格式如下的字符串:

    key1=value1 key2=value2 key3=value3
    

    或更通用的

    key1_fs2_value1_fs1_key2_fs2_value2_fs1_key3_fs2_value3

    使用fs1fs2 两个不同的字段分隔符

    您想使用这些值进行选择或一些操作。为此,最简单的方法是将它们存储在关联数组中:

    array["key1"] => value1
    array["key2"] => value2
    array["key3"] => value3
    array["key1","full"] => "key1=value1"
    array["key2","full"] => "key2=value2"
    array["key3","full"] => "key3=value3"
    

    这可以通过 awk 中的以下函数来完成:

    function str2map(str,fs1,fs2,map,   n,tmp) {
       n=split(str,map,fs1)
       for (;n>0;n--) { 
         split(map[n],tmp,fs2);
         map[tmp[1]]=tmp[2]; map[tmp[1],"full"]=map[n]
         delete map[n]
       }
    }
    

    因此,在处理完字符串后,您可以完全灵活地以任何您喜欢的方式进行操作:

    awk '
        function str2map(str,fs1,fs2,map,   n,tmp) {
           n=split(str,map,fs1)
           for (;n>0;n--) { 
             split(map[n],tmp,fs2);
             map[tmp[1]]=tmp[2]; map[tmp[1],"full"]=map[n]
             delete map[n]
           }
        }
        { str2map($0," ","=",map) }
        { print map["Var","full"] }
       ' file
    

    这种方法的优点是您可以轻松地调整代码以打印您感兴趣的任何其他键,甚至可以根据此进行选择,例如:

    (map["Version"] < 3) { print map["var"]/map["Len"] }
    

    【讨论】:

      【解决方案4】:

      由于评论中的讨论明确表明纯 bash 解决方案也是可以接受的:

      #!/bin/bash
      case $BASH_VERSION in
        ''|[0-3].*) echo "ERROR: Bash 4.0 required" >&2; exit 1;;
      esac
      
      while read -r -a words; do                # iterate over lines of input
        declare -A vars=( )                  # refresh variables for each line
        set -- "${words[@]}"                 # update positional parameters
        for word; do
          if [[ $word = *"="* ]]; then       # if a word contains an "="...
             vars[${word%%=*}]=${word#*=}    # ...then set it as an associative-array key
          fi
        done
        echo "${vars[Var]} $5"              # Here, we use content read from that line.
      done <<<"Version=2 Len=17 Hello Var=Howdy Other"
      

      &lt;&lt;&lt;"Input Here" 也可以是 &lt;file.txt,在这种情况下,文件中的行将被迭代。

      如果您想使用$Var 代替${vars[Var]},则用printf -v "${word%%=*}" %s "${word*=}" 代替vars[${word%%=*}]=${word#*=},并在其他地方删除对vars 的引用。请注意,这不允许像关联数组方法那样清理输入行之间的变量的好方法。

      【讨论】:

      • 这会为任何键返回相同的 Howdy Other
      • @Ajeetkumar,你确定你在支持关联数组的 shell 中运行它吗?如果您将 declare -A 更改为 declare -a 以徒劳地尝试与 4.0 之前的 bash 版本兼容,这就是您会遇到的问题。
      • 哦,我很抱歉这就是原因。我有 bash 3.2。
      • @Ajeetkumar,啊。我已经编辑了代码,用实际版本检查替换了需要 bash 4.0 的注释,这应该可以阻止其他人遇到同样的问题。
      【解决方案5】:

      我知道这尤其与 awk 有关,但提到这一点是因为许多人来到这里寻求解决方案来分解名称 = 值对(使用 / 不使用 awk 本身)。

      我发现下面的方式简单直接,并且在管理多个空格/逗号方面也非常有效 -

      来源:http://jayconrod.com/posts/35/parsing-keyvalue-pairs-in-bash

      change="foo=red bar=green baz=blue"
      
      #use below if var is in CSV (instead of space as delim)
      change=`echo $change | tr ',' ' '`
      
      for change in $changes; do
          set -- `echo $change | tr '=' ' '`
          echo "variable name == $1  and variable value == $2"
          #can assign value to a variable like below
          eval my_var_$1=$2;
      done
      

      【讨论】:

      • echo $change 非常有问题——考虑值为baz=* 的情况;您不希望将其替换为当前目录中以字符串 baz= 开头的文件名列表。而qux="hello world" 将被解析为qux="hello,后跟world" 作为它自己的单词。见DontReadLinesWithFor;并通过shellcheck.net 运行您的代码并修复它识别的错误。
      • 比这更糟糕的是使用eval -- 如果您在输入中将$(rm$(echo)-rf$(echo)~) 作为值,您不希望它被执行而不是按字面处理。
      • 有关如何安全执行间接分配的文档,请参阅BashFAQ #6。 (如果您信任变量名而不信任值,则可以安全地使用eval,但最好完全使用非eval 方法。
      • (这也不符合 OP 的要求,即忽略不包含 = 符号的单词)。
      • @CharlesDuffy 如前所述,它仅适用于 k=v 的简单情况。当时已经有其他答案了。我作为初学者添加了我无法首先找到一个简单的案例。我应该创建一个单独的 que 然后我自己。尽管感谢您的投入,但要知识渊博,您必须谦虚,尤其是对于那些不如您的人。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-12-19
      • 2015-07-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多