AWK:根据现有列创建新列

时间:2021-07-23 13:24:47

标签: awk

以下是我的文件的外观(按 $3 排序):

name_1|G1026|2017-08-27|2017-08-27|2017-09-02|19|19|21
name_2|G1566|2018-05-05|2018-05-05|2018-06-11|51|51|2B
name_2|G2124|2018-06-11|2018-06-11|2018-06-11|51|19|2B
name_2|G2125|2018-06-11|2018-06-11|2018-06-15|51|19|41
name_1|G4391|2020-08-14|2020-08-14|2020-08-20|19|19|21
name_1|G4392|2020-08-14|2020-08-20|2020-08-20|19|51|21
    

字段分隔符是 |。我正在尝试根据现有列向此文件添加一个额外的列 $9。对于 $1 中的多个名称实例,我想应用以下条件:

cond1 && (cond2 || cond3 || cond4) && (!cond5)

prevcur 是两行,它们的第一个字段相同,第三个字段非空,cur 之后的 prev。行 1,5 或 5,6 是具有第一个字段 name_1 的此类行对。行 2,3 和 3,4 是这样的行对,第一个字段为 name_2

delta = number-of-days(prev.$5 - cur.$4)prev.$5 过去 cur.$4 的天数。

条件是:

  1. cond1 = (0 <= delta <= 2 days)

    例如,对于 name_1 的第一个实例(第一行),检查 prev.$5 from 1st instance(1st row)是否在 0 到 2 天后 cur.$4 from 2nd instance (第 6 行)。

  2. cond2 = (prev.$6 == 51)

  3. cond3 = (cur.$7 == 51)

  4. cond4 = (cur.$8 == "2B" || cur.$8 == 41)

  5. cond5 = (prev.$6 == 19 && cur.$7 == 51 && cur.$8 == 21)

如果满足这些条件,则将列 $9 添加到两行中的第一行,以便输出如下所示。

name_1|G1026|2017-08-27|2017-08-27|2017-09-02|19|19|21
name_2|G1566|2018-05-05|2018-05-05|2018-06-11|51|51|2B|group1
name_2|G2124|2018-06-11|2018-06-11|2018-06-11|51|19|2B|group2
name_2|G2125|2018-06-11|2018-06-11|2018-06-15|51|19|41
name_1|G4391|2020-08-14|2020-08-14|2020-08-20|19|19|21
name_1|G4392|2020-08-14|2020-08-20|2020-08-20|19|51|21

添加的列以 group1 开头。每次添加一列时,前导数字都会增加。

如果所需的 prev.$cur.$ 值在一行中,那么我可以应用以下代码:

awk -F "|" '{if ($1=="name_1" && (($5-$4)<=2) && ($6==51||$7==51||$8==2B|41) &&($6!=19 && $7!=51 && $8!=21)) print $9="group1"}' OFS="|" 文件

任何有关如何使用 awk 解决此问题的线索将不胜感激!

1 个答案:

答案 0 :(得分:4)

以下需要 GNU awk 扩展 (mktime):

$ cat foo.awk
function d2ts(d) {
  gsub(/-/, " ", d)
  return mktime(d " 0 0 0")
}

BEGIN {
  f8["2B"] = 1;
  f8["41"] = 1;
}

FNR == NR {
  if($1 in ts && (f6[$1] == 51 || $7 == 51 || $8 in f8) &&
     !(f6[$1] == 19 && $7 == 51 && $8 == 21)) {
    delta = ts[$1] - d2ts($4)
    if(delta >= -12*3600 && delta <= 60*3600)
      change[nr[$1]] = 1
  }
  ts[$1] = d2ts($5)
  f6[$1] = $6
  nr[$1] = NR
  next
}

{
  if(FNR in change)
    $(NF+1) = "group" ++cnt
  print
}

$ awk -F'|' -f foo.awk OFS='|' file file
name_1|G1026|2017-08-27|2017-08-27|2017-09-02|19|19|21
name_2|G1566|2018-05-05|2018-05-05|2018-06-11|51|51|2B|group1
name_2|G2124|2018-06-11|2018-06-11|2018-06-11|51|19|2B|group2
name_2|G2125|2018-06-11|2018-06-11|2018-06-15|51|19|41
name_1|G4391|2020-08-14|2020-08-14|2020-08-20|19|19|21
name_1|G4392|2020-08-14|2020-08-20|2020-08-20|19|51|21

我们分 2 个阶段进行,这是 file 被传递两次的原因。第一遍检查所有条件并将要修改的记录编号存储在关联数组 change 中。第二阶段将最后一列添加到所有记录中,其编号是 change 关联数组的键。

<块引用>

注意:为了考虑夏令时和闰秒,日期比较不使用 0 天和 2 天阈值,而是减去半天 (-12*3600) 和 2 天半 ({{1} })。由于您的日期字段只有 1 天的分辨率,因此应该按预期运行。

说明:

  • d2ts 函数将 60*3600 日期转换为 UNIX 时间戳,即自 1970/01/01 以来的秒数。这是通过首先使用 YYYY-MM-DDgsub 转换为 YYYY-MM-DD(空格而不是 YYYY MM DD),连接 - 表示小时、分钟、秒,然后使用 0 0 0 转换为 UNIX 时间戳。

  • 当您的算法引用过去的行时,我们使用关联数组(mktimetsf6)来存储有关具有给定字段 #1 的最后遇到的行的信息价值。键是字段 #1 的值 (nr),值分别是字段 #5 的 UNIX 时间戳、字段 #6 的值和记录号。

  • 由于字段 #8 有多个候选值,我们在 name_X 部分定义了另一个关联数组 (f8),并使用 BEGIN 运算符作为测试。

当然,根据您的文件,您可能会在第一阶段遇到内存问题。例如,如果您有数十亿个不同的 in 值,则可能需要进行一些调整以避免由于关联数组的大小而导致内存溢出。