【问题标题】:How can i reoder lines in a text file based on a pattern?如何根据模式重新排序文本文件中的行?
【发布时间】:2020-01-17 16:24:00
【问题描述】:

我有一个包含 4 行批次的文本文件,每个批次的第一行位于正确的位置,但接下来的 3 行并不总是按正确的顺序排列。

name cat
label 4
total 5
value 4

name dog
total 4
label 3
value 6

name cow
value 6
total 1
label 4

name fish
total 3
label 5
value 6

我希望每 4 行批次采用以下格式:

name cat
value 4
total 5
label 4

所以输出将是:

name cat
value 4
total 5
label 4

name dog
value 6
total 4
label 3

name cow
value 6
total 1
label 4

name fish
value 6
total 3
label 5

该文件总共包含数千行,所以我想构建一个命令来处理这 3 行的所有潜在顺序,如果格式不正确,请重新排列它们。

我知道我可以使用 awk 搜索以特定字符串开头的行并重新排列它们:

awk '$1 == "value" { print $3, $4, $1, $2; next; } 1' 

但是我不知道如何实现类似的处理多行的东西。

我怎样才能做到这一点?

【问题讨论】:

    标签: shell awk sed grep


    【解决方案1】:

    通过将RS 设置为空字符串,由至少一个空行分隔的每个文本块都被视为一条记录。从那里可以很容易地捕获每个键值对并以所需的顺序输出它们。

    BEGIN {RS=""}
    {
        for (i=1; i<=NF; i+=2) a[$i] = $(i+1)
        print "name", a["name"] ORS \
              "value", a["value"] ORS \
              "total", a["total"] ORS \
              "label", a["label"] ORS
    }
    
    
    $ awk -f a.awk file
    name cat
    value 4
    total 5
    label 4
    
    name dog
    value 6
    total 4
    label 3
    
    name cow
    value 6
    total 1
    label 4
    
    name fish
    value 6
    total 3
    label 5
    

    【讨论】:

    • 您可以删除OFS,而只使用,。 print 语句将, 替换为OFS
    【解决方案2】:

    请您尝试关注一下。

    awk '
    /^name/{
      if(name){
        print name ORS array["value"] ORS array["total"] ORS array["label"] ORS
        delete array
      }
      name=$0
      next
    }
    {
      array[$1]=$0
    }
    END{
      print name ORS array["value"] ORS array["total"] ORS array["label"]
    }
    '  Input_file
    


    编辑:添加 Kvantour 先生建议的上述改进解决方案。

    awk -v OFS="\n" '
    (!NF) && ("name" in a){
      print a["name"],a["value"],a["total"],a["label"] ORS
      delete a
      next
    }
    {
      a[$1]=$0
    }
    END{
      print a["name"],a["value"],a["total"],a["label"]
    }
    '  Input_file
    

    【讨论】:

    • 这是一个很好的解决方案,我只会更改打印,而不是基于/name/,而是基于一个空行。像($NF==0) &amp;&amp; ("name" in a) { print array["name"] ORS array["value"] ORS array["total"] ORS array["label"] ORS; delete array; next } 这样的东西使它更短,更易读,更通用(假设name 不是第一个条目)
    • 在高尔夫中,它看起来像awk -v OFS="\n" '(!NF) &amp;&amp; ("name" in a) { print a["name"],a["value"],a["total"],a["label"] ORS; delete a;next}{a[$1]=$0}END { print a["name"],a["value"],a["total"],a["label"] }' file
    【解决方案3】:

    最简单的方法如下:

    awk 'BEGIN{RS=""; ORS="\n\n"; FS=OFS="\n"}
         { for(i=1;i<=NF;++i) { k=substr($i,1,index($i," ")-1); a[k]=$i } }
         { print a["name"],a["value"],a["total"],a["label"] }' file
    

    这是如何工作的?

    Awk 知道 recordsfields 的概念。文件在 records 中拆分,其中连续记录由记录分隔符 RS 拆分。每条记录都拆分为字段,其中连续字段由字段分隔符FS 拆分。默认情况下,记录分隔符 RS 设置为 字符 (\n),因此每条记录都是一行。记录分隔符的定义如下:

    RS: RS的字符串值的第一个字符为输入记录分隔符; 默认情况下。如果RS 包含多个字符,则未指定结果。 如果RS 为空,则记录由 加上一个或多个空行组成的序列分隔,前导或尾随空行不应导致输入开头或结尾处为空记录,而 始终是字段分隔符,无论 FS 的值是什么。

    所以用你给的文件格式,我们可以根据RS=""和字段分隔符`FS="\n"来定义记录。

    每条记录看起来都简化为:

    key1 string1      << field $1
    key2 string2      << field $2
    key3 string3      << field $3
    key4 string4      << field $4
    ...
    keyNF stringNF    << field $NF
    

    当 awk 读取记录时,我们首先通过将所有 key-value 对存储在数组 a 中来解析它。之后,我们要求打印我们觉得有趣的值。为此,我们需要定义输出字段分隔符OFS 和输出记录分隔符ORS

    【讨论】:

    • 嗨Kvantour,如果我可以问你,这个解决方案怎么样stackoverflow.com/a/59791392/5866580恕我直言,这看起来也更简单,你的意见是什么?
    • @RavinderSingh13 我对此发表了评论。我相信您的解决方案确实更简单。我只是喜欢利用 awk 的自然特性。
    【解决方案4】:

    在 Vim 中,您可以使用逆序对文件进行分段排序 sort!:

    for i in range(1,line("$"))
      /^name/+1,/^name/+3sort!
    endfor
    

    从 shell 发出的相同命令:

    $ ex -s '+for i in range(1,line("$"))|/^name/+1,/^name/+3sort!|endfor' '+%p' '+q!' inputfile
    

    【讨论】:

      猜你喜欢
      • 2013-06-20
      • 1970-01-01
      • 2016-07-31
      • 1970-01-01
      • 2016-10-06
      • 1970-01-01
      • 1970-01-01
      • 2013-07-01
      • 2016-02-18
      相关资源
      最近更新 更多