【问题标题】:bash string manipulation - regex match with delimiterbash 字符串操作 - 正则表达式与分隔符匹配
【发布时间】:2018-03-14 00:15:23
【问题描述】:

我有一个这样的字符串:

zone=INTERNET|status=good|routed=special|location=001|resp=user|switch=not set|stack=no|dswres=no|CIDR=10.10.10.0/24|allowDuplicateHost=disable|inheritAllowDuplicateHost=true|pingBeforeAssign=enable|inheritPingBeforeAssign=true|locationInherited=true|gateway=10.10.10.100|inheritDefaultDomains=true|inheritDefaultView=true|inheritDNSRestrictions=true|name=SCB-INET-A

分隔符| 内的顺序可以是随机的 - 这意味着键值对可以在字符串中随机排序。

我想要一个如下的输出字符串:

"INTERNET","10.10.10.0/24","SCB-INET-A"

输出中的所有值都是来自上述键值字符串的值

有谁知道我如何用 awksed 解决这个问题?

【问题讨论】:

  • 欢迎来到 StackOverflow!你试过什么?我们这里的大多数人都很乐意帮助你提高你的手艺,但作为短期无偿编程人员不太乐意。在MCVE 中向我们展示您迄今为止的工作、您期望的结果和您得到的结果,我们将帮助您解决问题。您已经为 bash、awk 和 sed 添加了标签,所以我希望在您的尝试中看到所有这三个中的代码。
  • 在您的帖子中也使用代码标签作为您的示例输入和示例输出。
  • 对我来说听起来像是一项家庭作业。请添加您尝试过的和不适合您的。
  • 如此独立于您的输入,您想输出区域、网关和名称的值吗?
  • @DmitriChubarov 好的,完成。请参阅您的答案。我没有反对它的冲动 - 你试了一下。

标签: bash awk sed


【解决方案1】:

假设您的输入是一个变量 var:

var="zone=INTERNET|status=good|routed=special|location=001|resp=user|switch=not set|stack=no|dswres=no|CIDR=10.10.10.0/24|allowDuplicateHost=disable|inheritAllowDuplicateHost=true|pingBeforeAssign=enable|inheritPingBeforeAssign=true|locationInherited=true|gateway=10.10.10.100|inheritDefaultDomains=true|inheritDefaultView=true|inheritDNSRestrictions=true|name=SCB-INET-A"

echo "$var" | tr "|" "\n" | sed -n -r "s/(zone|name|gateway)=(.*)/\"\2\"/p" 
"INTERNET"
"10.10.10.100"
"SCB-INET-A"

使用另外 2 个管道插入逗号并删除换行符:

SOFAR | tr "\n" "," | sed 's/,$//'

"INTERNET","10.10.10.100","SCB-INET-A"

【讨论】:

  • tr "\n" "," | sed 's/,$//' 可以改写为paste -sd,
【解决方案2】:

当您的输入中有名称 -> 值对时,最好的方法是创建这些映射的数组(下面的f[]),然后通过它们的名称访问这些值:

$ cat tst.awk
BEGIN { RS="|"; FS="[=\n]"; OFS="," }
{ f[$1] = "\"" $2 "\"" }
END { print f["zone"], f["CIDR"], f["name"] }

$ awk -f tst.awk file
"INTERNET","10.10.10.0/24","SCB-INET-A"

上述将有效地工作(即字面上比 shell 循环快几个数量级)并且可移植地在任何 UNIX 机器上的任何 shell 中使用任何 awk,这与迄今为止所有其他答案都依赖于非 POSIX 功能不同。与其他一些答案一样,它进行完整字符串匹配而不是部分正则表达式匹配,因此它非常健壮,并且在部分匹配的情况下不会导致错误的输出。它也不会像您的其他一些答案那样解释任何输入字符(例如转义序列和/或通配符),而是会在输出中按原样稳健地再现它们。

如果您需要增强它以打印任何额外的字段值,只需将它们作为 , f["<field name>"] 添加到 print 语句中,如果您需要更改输出格式或执行其他任何操作,这也绝对是微不足道的。

【讨论】:

    【解决方案3】:

    使用awk

    var="zone=INTERNET|status=good|routed=special|location=001|resp=user|switch=not set|stack=no|dswres=no|CIDR=10.10.10.0/24|allowDuplicateHost=disable|inheritAllowDuplicateHost=true|pingBeforeAssign=enable|inheritPingBeforeAssign=true|locationInherited=true|gateway=10.10.10.100|inheritDefaultDomains=true|inheritDefaultView=true|name=SCB-INET-A|inheritDNSRestrictions=true" 
    
    awk -v RS='|' -v ORS=',' -F= '$1~/zone|gateway|name/{print "\"" $2 "\""}' <<<"$var" | sed 's/,$//'
    "INTERNET","10.10.10.100","SCB-INET-A"
    

    输入记录分隔符RS 设置为|

    输入字段分隔符FS 设置为=

    输出记录分隔符ORS设置为,

    $1~/zone|gateway|name/ 正在过滤要提取的参数。 print 语句为参数值添加了双引号。

    sed 语句是为了删除烦人的最后一个,print 语句正在添加)。

    【讨论】:

      【解决方案4】:

      另一种使用 Bash 的解决方案。不是最短的,但我希望它是最好的可读性和最好的可维护性。

      #!/bin/bash
      
      # Function split_key_val()
      #   selects values from a string with key-value pairs
      # IN: string_with_key_value_pairs wanted_key_1 [wanted_key_2] ...
      # OUT: result
      function split_key_val {
        local KEY_VAL_STRING="$1"
        local RESULT
        # read the string with key-value pairs into array
        IFS=\| read -r -a ARRAY <<< "$KEY_VAL_STRING"
        #
        shift
        # while there are wanted-keys ...
        while [[ -n $1 ]]
        do
          WANTED_KEY="$1"
          # Search the array for the wanted-key
          for KEY_VALUE in "${ARRAY[@]}"
          do
            # the key is the part before "="
            KEY=$(echo "$KEY_VALUE" |cut --delimiter="=" --fields=1)
            # the value is the part after "="
            VALUE=$(echo "$KEY_VALUE" |cut --delimiter="=" --fields=2)
            if [[ $KEY == $WANTED_KEY ]]
            then
              # if result is empty; result= found value...
              if [[ -z $RESULT ]]
              then
                # (quote the damned quotes)
                RESULT="\"${VALUE}\""
              else
                # ... else add a comma as a separator
                RESULT="${RESULT},\"${VALUE}\""
              fi
            fi  # key == wanted-key
          done  # searched whole array
          shift # prepare for next wanted-key
        done
        echo "$RESULT"
        return 0
      }
      
      STRING="zone=INTERNET|status=good|routed=special|location=001|resp=user|switch=not set|stack=no|dswres=no|CIDR=10.10.10.0/24|allowDuplicateHost=disable|inheritAllowDuplicateHost=true|pingBeforeAssign=enable|inheritPingBeforeAssign=true|locationInherited=true|gateway=10.10.10.100|inheritDefaultDomains=true|inheritDefaultView=true|inheritDNSRestrictions=true|name=SCB-INET-A"
      
      split_key_val "$STRING" zone CIDR name
      

      结果是: "互联网","10.10.10.0/24","SCB-INET-A"

      【讨论】:

        【解决方案5】:

        不使用更复杂的文本编辑工具(作为练习!)

        $ tr '|' '\n' <file                          | # make it columnar
          egrep '^(zone|CIDR|name)='                 | # get exact key matches
          cut -d= -f2                                | # get values
          while read line; do echo '"'$line'"'; done | # quote values
          paste -sd,                                   # flatten with comma
        

        会给

        "INTERNET","10.10.10.0/24","SCB-INET-A"
        

        您也可以将while 语句替换为xargs printf '"%s"\n'

        【讨论】:

          【解决方案6】:

          不使用sedawk,而是使用 Bash 数组功能。

          line="zone=INTERNET|sta=good|CIDR=10.10.10.0/24|a=1 1|...=...|name=SCB-INET-A"
          
          echo "$line" | tr '|' '\n' | {
          declare -A vars 
          while read -r item ; do 
            if [ -n "$item" ] ; then 
               vars["${item%%=*}"]="${item##*=}"
            fi  
          done
          echo "\"${vars[zone]}\",\"${vars[CIDR]}\",\"${vars[name]}\"" ; }
          

          此方法的一个优点是您始终可以按顺序获取字段,而与输入行中字段的顺序无关。

          【讨论】:

          • 与 awk 解决方案相比,由于使用了 shell 循环(请参阅unix.stackexchange.com/questions/169716/…),上述方法的效率非常低。它还将解释输入中的转义序列,而不是按原样传递它们,并且它将扩展通配符以匹配本地文件名,因此输出将根据输入和运行它的目录的内容而有所不同。发明 shell 来操作文件和进程的人也发明了 awk 来调用 shell 来操作文本,所以这样做。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2010-10-13
          • 2016-10-22
          • 1970-01-01
          • 2011-04-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多