【问题标题】:Unix Parse Varying Named Value into seperate rowsUnix将命名值解析为单独的行
【发布时间】:2021-02-18 04:37:00
【问题描述】:

如下所述,我们得到了一个可变长度的输入文件。文字长度不一。

输入文件:

ID|Text
1|name1=value1;name3;name4=value2;name5=value5
2|name1=value1;name2=value2;name6=;name7=value7;name8=value8

此处的文本已命名值对作为内容,并且长度不等。请注意,文本列中的名称可以包含分号。我们正在尝试解析输入,但我们无法通过 AWK 或 BASH 处理它

期望的输出:

1|name1=value1
1|name3;name4=value2
1|name5=value5
2|name1=value1
2|name2=value2
2|name6=
2|name7=value7
2|name8=value8

以下代码片段适用于 ID=2,但不适用于 ID=1

echo "2|name1=value1;name2=value2;name6=;name7=value7;name8=value8" | while IFS="|"; read id text;do dsc=`echo $text|tr ';' '\n'`;echo "$dsc" >tmp;done
cat tmp
2|name1=value1
2|name2=value2
2|name6=
2|name7=value7
2|name8=value8
echo "1|name1=value1;name3;name4=value2;name5=value5" | while IFS="|"; read id text;do dsc=`echo $text|tr ';' '\n'`;echo "$dsc" >tmp;sed -i "s/^/${id}\|/g" tmp;done
cat tmp
1|name1=value1
1|name3
1|name4=value2
1|name5=value5

非常感谢任何帮助。

【问题讨论】:

  • 您提到过使用awkbash,但没有显示任何包含这些引用的代码;请查看how do I ask a good questionhow to create a minimal, reproducible example,然后考虑相应地更新您的问题(例如,提供示例输入、您的代码尝试、您的代码生成的(错误)输出和(正确)所需的输出)
  • 那么你发布的输出是你想要显示的输出还是你迄今为止尝试过的输出?
  • 你能告诉我们你正在寻找的输出吗?
  • 请编辑问题以包含您的其他详细信息;不是每个人都会花时间通读 cmets 试图拼凑出整个画面;谢谢
  • @markp-fuso - 这是一个无意的错字,现在更正了。我还添加了代码 sn-p 仅适用于 ID = 2 但不适用于 ID = 1

标签: bash shell awk scripting


【解决方案1】:

您能否尝试使用新版本的 GNU awk 中的示例进行跟踪、编写和测试。由于 OP 的 awk 版本较旧,因此如果有人拥有旧版本的 awk,请尝试将其更改为 awk --re-interval

awk '
BEGIN{
  FS=OFS="|"
}
FNR==1{ next }
{
  first=$1
  while(match($0,/(name[0-9]+;?){1,}=(value[0-9]+)?/)){
    print first,substr($0,RSTART,RLENGTH)
    $0=substr($0,RSTART+RLENGTH)
  }
}'  Input_file

输出如下。

1|name1=value1
1|name3;name4=value2
1|name5=value5
2|name1=value1
2|name2=value2
2|name6=
2|name7=value7
2|name8=value8

说明:为上述添加详细说明(以下仅用于说明目的)。

awk '                                        ##Starting awk program from here.
BEGIN{                                       ##Starting BEGIN section from here.
  FS=OFS="|"                                 ##Setting FS and OFS wiht | here.
}
FNR==1{ next }                               ##If line is first line then go next, do not print anything.
{
  first=$1                                   ##Creating first and setting as first field here.
  while(match($0,/(name[0-9]+;?){1,}=(value[0-9]+)?/)){
##Running while loop which has match which has a regex of matching name and value all mentioned permutations and combinations.
    print first,substr($0,RSTART,RLENGTH)    ##Printing first and sub string(currently matched one)
    $0=substr($0,RSTART+RLENGTH)             ##Saving rest of the line into current line.
  }
}' Input_file                                ##Mentioning Input_file name here.

【讨论】:

  • cat Input_file 1|name1=value1;name3;name4=value2;name5=value5 2|name1=value1;name2=value2;name6=;name7=value7;name8=value8 我在下面试过了,但它不返回任何输出。 awk ' 开始{ FS=OFS="|" } FNR==1{next} { first=$1 while(match($0,/(name[0-9]+;?){1,}=(value[0-9]+)?/)){ print首先,substr($0,RSTART,RLENGTH) $0=substr($0,RSTART+RLENGTH) } }' Input_file 当我使用 man awk 时,我看到我们有 gawk - 模式扫描和处理语言。如果我遗漏了什么,请告知
  • @seanarcher7,对不起,我没听明白(解决方案适用于给定的样本),请更清楚地解释你想在这里传达的内容。
  • 我使用了你给出的命令,但它没有返回任何输出。我们的 awk 版本是 3.1.7 awk --v GNU awk 3.1.7
  • @seanarcher7,我的解决方案需要新版本的 GNU awk。您可以尝试将awk ' 更改为awk --re-interval ' 一次吗?让 ERE 在旧 awks 中工作是一个旧选项,如果这对您有帮助,请告诉我?
  • 当然是拉文德。也感谢您提供帮助的 cmets。
【解决方案2】:

样本数据:

$ cat name.dat
ID|Text
1|name1=value1;name3;name4=value2;name5=value5
2|name1=value1;name2=value2;name6=;name7=value7;name8=value8

一个awk解决方案:

awk -F"[|;]" '                                                           # use "|" and ";" as input field delimiters
FNR==1 { next }                                                          # skip header line
       { pfx=$1 "|"                                                      # set output prefix to field 1 + "|"
         printpfx=1                                                      # set flag to print prefix

         for ( i=2 ; i<=NF ; i++ )                                       # for fields 2 to NF
             {
               if ( printpfx)     { printf "%s",   pfx  ; printpfx=0 }   # if print flag == 1 then print prefix and clear flag
               if ( $(i)  ~ /=/ ) { printf "%s\n", $(i) ; printpfx=1 }   # if current field contains "=" then print it, end this line of output, reset print flag == 1
               if ( $(i) !~ /=/ ) { printf "%s;",  $(i) }                # if current field does not contain "=" then print it and include a ";" suffix
             }
       }
' name.dat

以上生成:

1|name1=value1
1|name3;name4=value2
1|name5=value5
2|name1=value1
2|name2=value2
2|name6=
2|name7=value7
2|name8=value8

【讨论】:

  • @seanarcher7 听起来不错;正如 RavinderSingh 指出的那样,如果一个特定的答案最能解决您的问题,那么请考虑“接受”上述答案
【解决方案3】:

Bash 解决方案:

#!/usr/bin/env bash

while IFS=\| read -r id text || [ -n "$id" ]; do
  IFS=\; read -r -a kv_arr < <(printf %s "$text")
  printf "$id|%s\\n" "${kv_arr[@]}"
done < <(tail -n +2 a.txt)

一个普通的 POSIX shell 解决方案:

#!/usr/bin/env sh

# Chop the header line from the input file
tail -n +2 a.txt |
# While reading id and text Fields Separated by vertical bar
while IFS=\| read -r id text || [ -n "$id" ]; do
  # Sets the separator to a semicolon
  IFS=\;
  # Print each semicolon separated field formatted on
  # its own line with the ID
  # shellcheck disable=SC2086 # Explicit split on semicolon
  printf "$id|%s\\n" $text
done

输入a.txt:

ID|Text
1|name1=value1;name3;name4=value2;name5=value5
2|name1=value1;name2=value2;name6=;name7=value7;name8=value8

输出:

1|name1=value1
1|name3
1|name4=value2
1|name5=value5
2|name1=value1
2|name2=value2
2|name6=
2|name7=value7
2|name8=value8

【讨论】:

  • 非常感谢,但是 POSIX 解决方案对于 ID=1 存在与我报告的相同的问题。尾-n +2 a.txt | > 而 IFS=\|读取 -r id 文本 || [ -n "$id" ];做 > IFS=\; > printf "$id|%s\\n" $text > 完成 1|name1=value1 1|name3 1|name4=value2 1|name5=value5 2|name1=value1 2|name2=value2 2|name6= 2| name7=value7 2|name8=value8 相反,输出应该是这样的: 1|name1=value1 1|name3;name4=value2 1|name5=value5 2|name1=value1 2|name2=value2 2|name6= 2|name7 =value7 2|name8=value8
  • @seanarcher7 这正是我得到的输出。
  • @LéaGris;第2/3行输出需要合并成1|name3;name4=value2
【解决方案4】:

你有一些很好的答案,并且已经被接受了。这是一个更短的 gnu awk 命令,它也可以完成这项工作:

awk -F '|' 'NR > 1 {
   for (s=$2; match(s, /([^=]+=[^;]*)(;|$)/, m); s=substr(s, RLENGTH+1))
      print $1 FS m[1]      
}' file.txt
1|name1=value1
1|name3;name4=value2
1|name5=value5
2|name1=value1
2|name2=value2
2|name6=
2|name7=value7
2|name8=value8

【讨论】:

  • @seanarcher7:如果不起作用,请尝试此操作并在此处发表评论
猜你喜欢
  • 2019-05-07
  • 2016-12-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-14
  • 2020-03-25
  • 2016-12-07
  • 1970-01-01
相关资源
最近更新 更多