【问题标题】:awk arrange record order by field nameawk 按字段名排列记录顺序
【发布时间】:2021-08-28 00:37:50
【问题描述】:

我正在编写一个 bash 脚本来处理各种 LDAP 查询到管道分隔的文件中。某些结果记录不包括所有属性,并且每条记录的数据不会以每条记录的相同属性顺序返回。我编写了脚本以确保所有记录都具有 4 个必要的属性,现在我正在尝试使用 awk 对输出记录的字段重新排序,以使其全部匹配已建立的顺序。下面是我希望处理的一组示例记录,其中第一条记录代表所需的订单/列头。

cn: User ID
displayName: Display Name
LastLoginTime: Last Login
ExpirationDate: Exp Date

cn: mf_mdsa
displayName: NONE
ExpirationDate: 00000000000000
LastLoginTime: 20201220212738

displayName: NONE
cn: mf_cs
ExpirationDate: 00000000000000
LastLoginTime: 20201220212704

displayName: NONE
ExpirationDate: 00000000000000
LastLoginTime: 20181009205555
cn: OPERATOR

ExpirationDate: 00000000000000
displayName: HENNIE VAN DEVENTER
LastLoginTime: 20181030201447
cn: hvdevent

cn: A0S
displayName: LARA EVERWINE
ExpirationDate: 20190612001951
LastLoginTime: 20190313182136

cn: SODS822
ExpirationDate: 00000000000000
displayName: JAMES SIMS
LastLoginTime: 20210104000757

我正在如下调用一个 awk 脚本并输出到一个文件中

BEGIN {FS=": "; OFS="|"}
$1 == "cn" {cn = $2}
$1 == "displayName" {displayName = $2}
$1 == "LastLoginTime" {LastLoginTime = $2}
$1 == "ExpirationDate" {
        print cn,displayName,LastLoginTime,$2}

虽然似乎正在发生所需的重新排序,但一条记录中的字段值正在渗透到下一条记录中。我想这与如何将字段标签和值添加到变量中以提供打印有关,但我无法弄清楚如何在移动到下一个记录之前正确处理每条记录

User ID|Display Name|Last Login|Exp Date
mf_mdsa|NONE|Last Login|00000000000000
mf_cs|NONE|20201220212738|00000000000000
mf_cs|NONE|20201220212704|00000000000000
OPERATOR|NONE|20181009205555|00000000000000
A0S|LARA EVERWINE|20181030201447|20190612001951
SODS822|LARA EVERWINE|20190313182136|00000000000000
SODS822|JAMES SIMS|20190313182136|00000000000000

【问题讨论】:

  • 您的示例输出与您的示例输入不匹配...

标签: awk


【解决方案1】:

您可以通过设置RS= FS='\n' 来打破\n\n 的记录并将字段作为一行来利用awk 的paragraph mode,而不是设置FS=": "。然后在: 上拆分该行

由于 awk 数组是无序的,因此您需要保留一个顺序索引。在这种情况下,顺序由第一条记录的顺序决定。通过将此类分配给 order 而不是从第一条记录中读取它,很容易将其更改为不同的顺序。

这是一个例子(可能没有优化...)

awk -v RS= -v FS='\n' 'FNR==1 {
                        for(i=1;i<=NF;i++){
                            split($i,x,/:[ ]*/)
                            order[i]=x[2]
                            key[x[2]]=x[1]
                        }
                        len=i-1
                        for(i=1; i<=len; i++) 
                            printf "%s%s", order[i], i<len ? "|" : ORS
                        next
                        }
                        split("",field)
                        {for(i=1;i<=NF;i++) {
                            split($i,x,/:[ ]*/)
                            field[x[1]]=x[2]
                        }
                    for(i=1;i<=len;i++) {
                        printf "%s%s", field[key[order[i]]], i<len ? "|" : ORS
                    }
}' file 

使用 ruby​​ 会更容易一些。 Ruby 支持段落模式(使用-00 开关)、有序散列和多行正则表达式。因此,awk 可以简化为:

ruby -00 -lne '
    data=$_.scan(/^([^:]+):[ \t]*(.+)$/).to_h
    if $.==1 
        puts data.values.join("|")
        order=data
    else
        puts order.map {|k,v| data[k]}.join("|")
    end
' file

这些印刷品之一:

User ID|Display Name|Last Login|Exp Date
mf_mdsa|NONE|20201220212738|00000000000000
mf_cs|NONE|20201220212704|00000000000000
OPERATOR|NONE|20181009205555|00000000000000
hvdevent|HENNIE VAN DEVENTER|20181030201447|00000000000000
A0S|LARA EVERWINE|20190313182136|20190612001951
SODS822|JAMES SIMS|20210104000757|00000000000000

【讨论】:

    【解决方案2】:

    由于您的字段顺序在每组中不同,您应该只使用条件!NF 在空行写入完整记录(假设您在每 4 行之后有一个换行符,如问题所示):

    cat rec.awk
    
    BEGIN {FS=": "; OFS="|"}
    $1 == "cn" {cn = $2}
    $1 == "displayName" {displayName = $2}
    $1 == "LastLoginTime" {LastLoginTime = $2}
    $1 == "ExpirationDate" {expirationDate=$2}
    !NF {if (cn != "") print cn,displayName,LastLoginTime,expirationDate; cn=""}
    END {if (cn != "") print cn,displayName,LastLoginTime,expirationDate}
    
    

    然后将其用作:

    awk -f rec.awk file
    
    User ID|Display Name|Last Login|Exp Date
    mf_mdsa|NONE|20201220212738|00000000000000
    mf_cs|NONE|20201220212704|00000000000000
    OPERATOR|NONE|20181009205555|00000000000000
    hvdevent|HENNIE VAN DEVENTER|20181030201447|00000000000000
    A0S|LARA EVERWINE|20190313182136|20190612001951
    

    【讨论】:

    • 谢谢,@anubhava。 !NF 是我正在寻找的。我还发现最后一条记录不会得到处理,除非我在它后面有一个空行,因为 awk 用它来表示记录的结束。
    • @kkeniston:我添加了一个END 块来解决最后一条记录后缺少空白链接的情况。
    【解决方案3】:

    您无需提前处理数据以确保每条记录都包含全部 4 个字段,也无需在脚本中硬编码字段名称,因为您已经在数据顶部提供了字段名称。

    给定这个脚本(在每个 Unix 机器上的任何 shell 中使用任何 awk):

    $ cat tst.awk
    BEGIN {
        FS = RS
        RS = ""
        OFS = "|"
    }
    {
        delete tag2val
        for (i=1; i<=NF; i++) {
            tag = val = $i
            sub(/:.*/,"",tag)
            sub(/[^:]*: */,"",val)
            tag2val[tag] = val
            if ( NR == 1 ) {
                tags[++numTags] = tag
            }
        }
    
        for (tagNr=1; tagNr<=numTags; tagNr++) {
            tag = tags[tagNr]
            val = (tag in tag2val ? tag2val[tag] : "NONE")
            printf "%s%s", val, (tagNr<numTags ? OFS : ORS)
        }
    }
    

    这个输入:

    $ cat file
    cn: User ID
    displayName: Display Name
    LastLoginTime: Last Login
    ExpirationDate: Exp Date
    
    cn: mf_mdsa
    ExpirationDate: 00000000000000
    LastLoginTime: 20201220212738
    
    cn: mf_cs
    ExpirationDate: 00000000000000
    LastLoginTime: 20201220212704
    
    LastLoginTime: 20181009205555
    cn: OPERATOR
    
    cn: hvdevent
    
    ExpirationDate: 20190612001951
    
    displayName: JAMES SIMS
    LastLoginTime: 20210104000757
    

    我们得到这个输出:

    $ awk -f tst.awk file
    User ID|Display Name|Last Login|Exp Date
    mf_mdsa|NONE|20201220212738|00000000000000
    mf_cs|NONE|20201220212704|00000000000000
    OPERATOR|NONE|20181009205555|NONE
    hvdevent|NONE|NONE|NONE
    NONE|NONE|NONE|20190612001951
    NONE|JAMES SIMS|20210104000757|NONE
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-08
      相关资源
      最近更新 更多