unix - awk 程序文件执行

标签 unix awk

由于我的最后一个问题太长,这里有一个包含当前代码级别的精简版本。

摘要:我需要接受一个以竖线分隔的输入文件,检查以确保所有适用的记录类型都存在,添加任何缺失的记录类型,并验证/更正每个记录类型中的子字段数量。

输入记录:

AA|1234|ABCD|EDGFT|TR56BE|~BB||E5TGE|~CC|253641|84597|~DD|78HND|ACBE|||43|~EE|HISBL|78943|~FF|12345|SKIP|~GG|||TYBGFR
AA|2345|CDEF|GFHIT|48UJKK|~CC||3FKTI

记录类型和子字段计数验证文件 known_flds 条目:

AA~5~req
BB~2~opt
CC~3~opt
DD~6~opt
EE~4~opt
FF~2~skp
GG~4~opt

当前脚本,没有子字段更正:

#!/usr/bin/awk -f

BEGIN { FS=OFS="~" }

FNR==NR {
    dflts[$1] = create_empty_field($1,$2)
    if( $3 ~ /req|opt/ ) fld_order[++fld_cnt] = $1
    fld_rule[$1] = $3
    next
}

{
    flds = ""
    j = 1
    for(i=1; i<=fld_cnt; i++) {
        j = skip_flds( j )

        if($j !~ ("^" fld_order[i])) fld = dflts[fld_order[i]]
        else { fld = $j; j++ }
        flds = flds (flds=="" ? "" : OFS) fld
    }
    print flds
}

function create_empty_field(name, cnt,     fld, i) {
    fld = name
    for(i=1; i<=cnt; i++) { fld = fld "|" }
    return( fld )
}

function skip_flds(fnum,     name) {
    name = $fnum
    sub(/\|.*$/, "", name)
    while(fld_rule[name] == "skp") {
        fnum++
        name = $fnum
        sub(/\|.*$/, "", name)
    }
    return( fnum )
}

我最初尝试执行子字段的验证和更正:

#!/usr/bin/awk -f

BEGIN { FS=OFS="~" }

FNR==NR {
    dflts[$1] = create_empty_field($1,$2)
    if( $3 ~ /req|opt/ ) fld_order[++fld_cnt] = $1
    fld_rule[$1] = $3
    next
}

{
    flds = ""
    j = 1
    for(i=1; i<=fld_cnt; i++) {
        j = skip_flds( j )
        if($j !~ ("^" fld_order[i])) fld = dflts[fld_order[i]]
        else { fld = fix_sub($j,$2); j++ }
        flds = flds (flds=="" ? "" : OFS) fld
    }
    print flds
}

function create_empty_field(name, cnt,     fld, i) {
    fld = name
    for(i=1; i<=cnt; i++) { fld = fld "|" }
    return( fld )
}

function skip_flds(fnum,     name) {
    name = $fnum
    sub(/\|.*$/, "", name)
    while(fld_rule[name] == "skp") {
        fnum++
        name = $fnum
        sub(/\|.*$/, "", name)
    }
    return( fnum )
}

function fix_sub(rec, num,  upd, cnt) {
    cnt=split(rec,a,"|")-1
    upd=""
    if(cnt != num) 
      {for(i=1;i<=$num;i++) 
       upd = upd a[i] "|" }
    else { upd=$rec }
    return(upd)
}

当到达第二个记录类型时,上面会导致错误。所以现在我知道我需要从 known_flds 文件中捕获第二个值,以便将其传递给 fix_sub 函数。

我将添加:

        sub_fld[$1] = $2

FNR==NR部分,但除此之外,我的大脑简直崩溃了,我无法继续前进。

我知道 fix_sub 区域作为一个独立的区域可以工作。现在我只需要从 known_flds 读取值即可传递。

所需的输出是:

AA|1234|ABCD|EDGFT|TR56BE|~BB||~CC|253641|84597|~DD|78HND|ACBE|||43|~EE|HISBL|78943||~GG|||TYBGFR
AA|2345|CDEF|GFHIT|48UJKK|~BB||~CC||3FKTI|~DD||||||~EE||||~GG|||

原问题:UNIX Shell Script Solution for formatting a pipe-delimited, segmented file

最佳答案

尝试这个修改后的脚本:

#!/usr/bin/awk -f

BEGIN { FS=OFS="~" }

FNR==NR {
    dflts[$1] = create_empty_field($1,$2)
    if( $3 ~ /req|opt/ ) {
        fld_order[++fld_cnt] = $1
        subfld_cnt[$1] = $2
    }
    fld_rule[$1] = $3
    next
}

{
    flds = ""
    j = 1
    for(i=1; i<=fld_cnt; i++) {
        j = skip_flds( j )
        if($j !~ ("^" fld_order[i])) fld = dflts[fld_order[i]]
        else { fld = fix_sub(j); j++ }
        flds = flds (flds=="" ? "" : OFS) fld
    }
    print flds
}

function get_field_name(fnum,      name) {
    name = $fnum
    sub(/\|.*$/, "", name)
    return( name )
}

function create_empty_field(name, cnt,     fld, i) {
    fld = name
    for(i=1; i<=cnt; i++) { fld = fld "|" }
    return( fld )
}

function skip_flds(fnum,     name) {
    name = get_field_name(fnum)
    while(fld_rule[name] == "skp") {
        fnum++
        name = $fnum
        sub(/\|.*$/, "", name)
    }
    return( fnum )
}

function fix_sub(fnum,       name, cnt, a, scnt, i, upd) {
    name = get_field_name(fnum)
    cnt = split($fnum, a, "|")-1
    scnt = subfld_cnt[ name ]
    if(cnt != scnt) {
        for(i=1;i<=scnt;i++)
            upd = upd a[i] "|"
        return( upd )
    }
    return( $fnum )
}

主要区别:

  • subfld_cnt[$1] = $2 已添加到 FNR==NR block 中的 req|opt 部分(处理 known_flds 文件)
  • 添加了 get_field_name() 函数,该函数返回由其 fnum 参数指定的字段的第一个子字段。
  • 从函数 skip_flds() 调用 get_field_name()
  • 修改了 fix_sub() 以仅采用 fnum(所有其他变量都是函数的本地变量)并在必要时修复子字段管道的数量。现在,对它的调用仅需要一个 j 参数,如 fix_sub(j) 中所示。

fix_sub() 更改的详细信息:

  • name = get_field_name(fnum) 获取用于查找的字段名称
  • 分割 $fnum,并获取分割的计数(保留 -1 调整)
  • scnt = subfld_cnt[ name ] 从添加到 known_flds 文件处理中的数组中获取所需的字段计数。这是你缺少的主要部分。
  • cnt != scnt时修复子fld。
  • 保留 upd 设置代码,但删除了 upd = "" - 这已经针对局部变量完成了。
  • 个人偏好 - 直接返回任一值而不是 else

我得到以下信息:

AA|1234|ABCD|EDGFT|TR56BE|~BB||~CC|253641|84597|~DD|78HND|ACBE|||43|~EE|HISBL|78943
||~GG|||TYBGFR|
AA|2345|CDEF|GFHIT|48UJKK|~BB||~CC||3FKTI|~DD||||||~EE||||~GG||||

这与您想要的输出不完全匹配。区别在于 GG 字段中最后的 |。我认为您想要的输出缺少它。否则,在所有其他处理之后只需删除最终字段的最终管道。

关于unix - awk 程序文件执行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30491496/

相关文章:

ubuntu - espdiff - 它是什么以及如何使用它?

linux - 如何编写跟随光标的 X11 应用程序

awk - 使用awk对两列进行分组和求和

perl - 如何仅在您指定的某个任意字段上进行正则表达式替换

unix - 获取默认/首选文件扩展名

python - 想要从用于将文件从一个服务器传输到另一个服务器的 scp 命令的 shell 脚本传递密码

regex - awk 的 ^(插入符号)排除是如何工作的?

linux - Gawk 在找到 3 行之间的最小值时无法正常工作

多种模式的awk

java - unix 时间 - 在 Java 中计算当前小时