UNIX Shell脚本解决方案,用于格式化以管道分隔的分段文件

时间:2015-05-22 17:03:56

标签: unix awk grep sql-loader

输入文件在同一行中最多有34种不同的记录类型。

文件是以管道分隔的,每个记录类型用'〜'分隔(原始记录类型除外。

并非所有34种记录类型都包含在每一行中,我不需要全部。

所有记录类型都将按指定的顺序发送,但不会始终发送所有记录类型。第一个记录类型是强制性的,将始终发送。在34种类型中,只有7种是强制性的。

每个记录类型都有预定义数量的字段,如果客户端和负载之间没有适当的准备时间,则永远不应偏离该定义。

将根据所需的记录类型构建Oracle表,其中包含所有必需的列。因此,一行将包含与输入文件类似的每种记录类型的信息,但还会包含来自某些未包含在输入中的记录类型的列的空值。

我正在寻找的最终结果是一种对输入文件执行条件格式化的方法,以便生成一个输出,可以通过sqlldr简单地加载到shell脚本中,而不是通过PL / SQL(我想要的)我的非PL / SQL同事能够解决/修复加载过程中遇到的任何问题。)

包含3条记录的小例子(本例中数据类型无关紧要):

Record Types:  AA, BB, CC, DD, EE, FF  
AA has 5 fields (Mandatory)  
BB has 2 fields (Optional)  
CC has 3 fields (Optional)  
DD has 6 fields (Optional)  
EE has 4 fields (Optional)  
FF has 2 fields (Not needed.  Skipping in output)  
GG has 4 fields (Optional)


AA|12345|ABCDE|67890|FGHIJ|~BB|12345|~CC|ABCDE|12345|~DD|A|B|C|D|E|~EE|1|2|3|~FF|P|~GG|F|R|T
AA|23456|BCDEF|78901|GHIJK|~CC|BCDEF|23456|~EE|2|3|4|~GG|R|F|G
AA|34567|CDEFG|89012|HIJKL|~DD|B|C|D||~FF|Q

第1行没有问题,因为它有所有可用的记录类型,但第2行和第3行没有。因此需要修改它们以包含缺少的记录类型。整体输出需要看起来像这样:

AA|12345|ABCDE|67890|FGHIJ|~BB|12345|~CC|ABCDE|12345|~DD|A|B|C|D|E|~EE|1|2|3|~GG|F|R|T
AA|23456|BCDEF|78901|GHIJK|~BB||~CC|BCDEF|23456|~DD||||||~EE|2|3|4|~GG|R|F|G
AA|34567|CDEFG|89012|HIJKL|~BB||~CC|||~DD|B|C|D||~EE||||~GG|||

我已经开始记录每条记录,将其拆分为自己的文件,然后使用:

typeset -i count=0
while read record
do
newfile="`echo $file`.$count.dat"
echo $record | sed 's/|~/\n/g' > $newfile
count=$count+1
done < $file 

将每个记录类型放在所述文件中的自己的行上,但是将其重新组合成一行并显示所有可能的字段非常棘手。这显然不是最好的方法,因为每个文件可以有几千条记录,这会产生几千个文件,但我用它作为起点来降低逻辑。

有什么想法吗?

1 个答案:

答案 0 :(得分:2)

这是一个可执行的awk脚本解决方案,它不是非常严格,但可以帮助您入门:

#!/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 )
}

它需要一个额外的输入文件来指定每种类型字段的默认值,我称之为&#34; known_flds&#34;

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

与数据文件具有相同的分隔符,因为我不想在脚本或输入文件之间添加FS切换。它是您的字段要求的编码。最后一个字段是:

的简写
  • req - &gt;强制性(输入或输出或两者兼而有之?)
  • opt - &gt;可选(仅在输入中可选)
  • skp - &gt;跳过(在输出中)

awk.script成为可执行文件并像./awk.script known_flds data一样运行时,我得到以下输出:

AA|12345|ABCDE|67890|FGHIJ|~BB|12345|~CC|ABCDE|12345|~DD|A|B|C|D|E|~EE|1|2|3|~GG|F|R|T
AA|23456|BCDEF|78901|GHIJK|~BB||~CC|BCDEF|23456|~DD||||||~EE|2|3|4|~GG|R|F|G
AA|34567|CDEFG|89012|HIJKL|~BB||~CC|||~DD|B|C|D||~EE||||~GG||||

问题数据中的G字段似乎没有指定正确数量的字段,或者在输入数据中缺少尾随管道。

我至少做了以下假设:

  • 文件中的每个字段都是正确的 - 字段本身不需要填充
  • 字段的顺序正确,包括应跳过的字段。
  • 任何行都可能缺少可选字段,并且任何缺少的可选字段应在输出中显示为空字段。
  • 可以从known_flds文件中指定字段顺序。否则,我可能已经选择了要完成的文件的第一行,并按正确的字段顺序包含输出所需的所有字段。但这并不允许将字段称为必填字段。

这是一个简单的剧本细分:

  • FNR==NR - 解析原始文件并使用create_empty_field()函数创建默认空字段,并按字段名称将结果放入dflts。创建基本字段顺序,将其存储在fld_order数组中。跳过的字段不会放入fld_order,而是所有字段&#34;规则&#34;被添加到fld_rule数组。
  • 将检查所有行。检查字段顺序,仅尝试打印任何记录的fld_cnt字段。超出known_flds中的行数的任何字段都不会输出。
  • 对于任何记录,请跳过opt字段并增加j
  • 使用flds的当前字段构建$j变量,或者是否显示缺少字段,dflts为空字段。
  • 使用其他空字段打印flds,但不跳过字段。

这是功能细分

create_empty_field()

  • name, cnt是来自第一个文件的参数,而fld, i是局部变量,设置为空值以便在函数中使用。
  • fld设为name$1 known_flds
  • 生成最高cnt值的管道($2来自known_flds)。

skip_flds()

  • fnum是记录字段编号的参数,而name是本地变量
  • name
  • 中提取$fnum部分
  • 检查是否应该使用fld_rule[name] == "skp"测试跳过它。
  • 如果应该跳过,请递增fnum并重置name变量。
  • 我认为重复的name =sub调用行应该是一个新函数,但我在这里没有这样做。

基本上,我在known_flds中制作解析/转换规则,然后使用awk.scriptdata文件中的记录进行解释/强制执行。虽然这是一个合理的开始,但是当不存在manadatory字段或者是空的时,你可以另外将错误打印到另一个文件,将缺少的子字段添加到字段等等。你可能会变得如此复杂。