【问题标题】:awk | Add new row or update existing row in a file哇 |在文件中添加新行或更新现有行
【发布时间】:2013-12-09 04:28:33
【问题描述】:

我想在file2的基础上更新file1。如果 file2 中有任何新行,则应将其添加到 file1 中。如果 file2 中的任何行已经在 file1 中,则如果 file2 中的时间更长,则使用 file2 中的行更新该行。

文件1

DL,1111111100,201312051013,val,FIX01,OptIn,N,Ext1,Ext2  
DL,1111111101,201312051014,val,FIX01,OptIn,Y,Ext1,Ext2  
DL,1111111102,201312051015,val,FIX01,OptIn,Y,Ext1,Ext2  
DL,1111111103,201312051016,val,FIX01,OptIn,N,Ext1,Ext2  

文件2

DL,1111111101,201312041013,val,FIX02,OptIn,N,Ext1,Ext2  
DL,1111111102,201312051016,val,FIX02,OptIn,N,Ext1,Ext2  
DL,1111111102,201312051017,val,FIX02,OptIn,N,Ext1,Ext2  
DL,1111111104,201312051014,val,FIX01,OptIn,Y,Ext1,Ext2  
DL,1111111104,201312051016,val,FIX02,OptIn,Y,Ext1,Ext2  

新文件1

DL,1111111100,201312051013,val,FIX01,OptIn,N,Ext1,Ext2  
DL,1111111101,201312051014,val,FIX01,OptIn,Y,Ext1,Ext2  
DL,1111111102,201312051017,val,FIX02,OptIn,N,Ext1,Ext2  
DL,1111111103,201312051016,val,FIX01,OptIn,N,Ext1,Ext2  
DL,1111111104,201312051016,val,FIX02,OptIn,Y,Ext1,Ext2  

注意事项:

  • 第二个字段在输出中应该是唯一的。
  • 添加新值:在日期列(第三个字段)的基础上,取 file2 中值“1111111104”的最新第二个字段(201312051016)然后旧值(201312051014)。
  • 更新现有值:根据第 3 列中的日期将“1111111102”更新为新值
  • file1 非常大,而 file2 只有 5-10 个条目。
  • 第二个字段“1111111101”的行不需要更新,因为与file2 中的新日期“201312041013”相比,file1 中的条目已经具有最新日期“201312051014”。

我没有对此进行太多尝试,因为它对我作为初学者来说确实有复杂的条件..

BEGIN { FS = OFS = "," }  
FNR == NR {  
  m=$2;  
  a[m] = $0;  
  next  
}  
{  
  if($2 in a)  
  {  
        split(a[$2],datetime,",")  
        if($3>datetime[3])  
                print $0;  
        else  
                print a[$2]"Old time"  
  }  
  else print $0"NOMATCH";  
  delete a[$2];  
}  

【问题讨论】:

  • 问题是什么?我不知道你想要达到什么目的。试着解释你想要什么,而不是仅仅发布一些文件和难以理解的笔记 - 然后有人会提供帮助。
  • 如果我不能正确解释,我很抱歉。现在我也添加了小摘要。如果我需要解释,请告诉我。

标签: awk


【解决方案1】:

假设您可以按如下方式启动您的awk

awk -f script.awk input2.csv input1.csv > result.csv

您可以使用以下脚本来获得所需的输出:

BEGIN  {
    FS = OFS = "," 
}
FILENAME == "input2.csv" {
    date[$2] = $3
    data[$2] = $0
    used[$2] = 0
}
FILENAME == "input1.csv" {
    if ($2 in date) {
        used[$2] = 1
        if ($3 < date[$2])
            print data[$2]
        else
            print $0
    }  else {
        print $0
    }
}
END {
    for (key in used) {
        if (used[key] == 0)
            print data[key]
    }
}

注意事项:

  • 该脚本利用了 file2 小于 file1 的假设,因为它仅对 file2 中的少数条目使用数组。
  • 新条目只是简单地附加到输出中。没有排序。如果需要这样做,则必须付出额外的努力。

编辑

注意@JonathanLeffler 关于我确定正在处理哪个文件的方式的评论,我想提供一个替代版本,它可能(或可能不是:-))比检查NR=FNR 更容易理解。但是,它仅适用于足够新的awk 版本,它们能够将数组的大小返回为length(array)

BEGIN {
    FS = ","
}
{
    # The following effectively creates an array entry for each filename found (for "known" filenames existing entries are overwritten).
    files[FILENAME] = 1
    # check the number of files we have so far
    if (length(files) == 1) {
        # we are still in the first file
        date[$2] = $3
        data[$2] = $0
        used[$2] = 0
    } else {
        # we are in the second file (or any other following file)
        if ($2 in date) {
            used[$2] = 1
            if ($3 < date[$2])
                print data[$2]
            else
                print $0
        }  else {
            print $0
        }
    }
}
END {
    for (key in used) {
        if (used[key] == 0)
            print data[key]
    }
}

此外,如果您需要根据第二行对输出进行排序,您可以将对 awk 的调用替换为:

awk -f script.awk input2.csv input1.csv | sort -t "," -n -k 2  > result.csv

当然,后者适用于脚本的两个版本。

【讨论】:

  • 基本逻辑看起来不错,并且与我使用的非常相似(我从数组中删除了已使用的条目;您有辅助数组used 来传达相同的信息)。但是,通过键入这两个文件名,您的代码会变得非常脆弱,而我的代码可以与input2.csvinput1.csv 以及file2file1 一起使用。在某些方面,FNR == NR 惯用语不太讨人喜欢,但它确实可以很好地处理第一个文件与其他文件的区别。
  • 谢谢马库斯..你的解决方案和乔纳森一样效果很好.. :) 你摇滚
【解决方案2】:

由于file1 很大但file2 很小(5-10 个条目),您需要先将所有file2 读入内存,处理重复值。结果,您将拥有一个由记录号和新数据索引的数组;您还应该在单独的数组中记录每条记录的日期。然后,当您阅读主文件时,您会在数组中查找记录号和日期,如果需要,将保存的新记录替换为传入的旧记录。

您的大纲脚本大部分都在那里。它更复杂,因为您没有保存传入的日期。这或多或少有效:

awk -F, '
FNR == NR { if (!($2 in date) || date[$2] < $3) { date[$2] = $3; line[$2] = $0; } next; }
          { if ($2 in date)
            {
                if (date[$2] > $3)
                    print line[$2]
                else
                    print
                delete line[$2]
                delete date[$2]
            }
            else
              print
          }
END       { for (l in line) print line[l]; }' file2 file1

给定数据的示例输出:

DL,1111111100,201312051013,val,FIX01,OptIn,N,Ext1,Ext2
DL,1111111101,201312051014,val,FIX01,OptIn,Y,Ext1,Ext2
DL,1111111102,201312051017,val,FIX02,OptIn,N,Ext1,Ext2
DL,1111111103,201312051016,val,FIX01,OptIn,N,Ext1,Ext2
DL,1111111104,201312051016,val,FIX02,OptIn,Y,Ext1,Ext2

但是,如果有 4 条新记录,则无法保证它们会按排序顺序排列,尽管它们都将位于列表的末尾。可以升级脚本以在列表中的适当位置打印新记录如果保证输入按排序顺序。您只需搜索行列表以查看是否有任何行应在当前行之前打印,如果是,则执行此操作(并删除记录,以便它们不会在末尾打印)。

请注意,输出的唯一性取决于输入的唯一性 (file1)。也就是说,如果输入中的字段 2 重复,则此代码不会注意到。即使发现重复项,当前的设计也无能为力;旧行已打印,因此打印新行只会导致重复。如果您担心这一点,您可以设计awk 脚​​本以将整个file1 保留在内存中,并且仅在处理了整个输入时才打印任何内容。不用说,这比当前设计使用了更多的内存,因此通常效率会降低。不过,如果需要,也可以这样做。

【讨论】:

  • 谢谢乔纳森..这对你来说又快又容易..你能否解释一下第二个如果条件..?第二个“if”以“{”开头..这是第一个 if 条件之后文件上的第二个循环吗?
  • 共有三个区块。第一个是FNR == NR 块;它处理第一个处理的文件,又名file2(因为那是文件记录号与总记录号匹配的时候,并且因为next 跳过了给定行的其余处理)。下一个块是您所指的块(我认为);它适用于第二个文件中的每一行。它查看记录号是否已知,如果已知并且替换日期比文件中的日期(file1)更新,则打印修订;无论哪种方式,它都会删除清洁记录。
  • 如果记录不在file2数据中,则简单地打印记录。第三个块是END 块。它查找仍然留在line 数组中的任何记录并打印它们;它们与 file1 中的任何内容都不匹配,因此必须添加它们。
猜你喜欢
  • 1970-01-01
  • 2022-08-19
  • 2018-02-06
  • 2021-06-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多