【问题标题】:UNIX Shell Script Solution for formatting a pipe-delimited, segmented file用于格式化管道分隔的分段文件的 UNIX Shell 脚本解决方案
【发布时间】:2015-05-23 05:08:28
【问题描述】:

输入文件在同一行中最多有 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 

将每个记录类型放在所述文件中自己的行中,但是将其回滚到包含所有可能字段的一行中是非常棘手的。这显然不是最好的方法,因为每个文件可以有几千条记录,这将导致几千个文件,但我以此为起点来降低逻辑。

有什么想法吗?

【问题讨论】:

  • 听起来像是一个可怕的组织。你确定你不会对 N 个文件做得更好吗,每个记录类型一个?
  • 每个记录类型的某个地方是否有文档信息,需要多少个字段,以及它们出现的顺序?或者字段集可以按任何顺序排列,因此您可以在一行中按该顺序排列~EE、~DD、~CC、~BB、~AA?脚本如何知道应该存在哪些记录?如果遇到不应该存在的记录应该怎么办(示例中的 ~FF 有 5 种记录类型)?如果一切都已排序,如果检测到乱序数据该怎么办?
  • 请使用额外信息更新问题(然后删除这些 cmets)。还有两个问题:(1)如果~AA 应该总是有5 个字段,如果遇到只有4 个字段的记录,或者~AA 有6 个字段会发生什么? (2) 是否存在一种将始终存在的记录类型,或者它们都是可选的?
  • 问题已更新。
  • 感谢您更新问题。这是正常的做法——这个问题需要尽可能地独立,因为 cmets 名义上是“瞬态的”;它们可以被删除,但问题应该继续易于理解和回答。

标签: unix awk grep sql-loader


【解决方案1】:

这是一个不完全严格但可以帮助您入门的可执行 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 )
}

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

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

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

  • req -> 强制(在输入或输出或两者中?)
  • opt -> 可选(仅在输入中可选)
  • skp -> 跳过(在输出中)

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,而是将所有字段“规则”添加到fld_rule 数组中。
  • 将检查所有行。检查字段顺序,只尝试打印出任何记录的fld_cnt 字段。 known_flds 中超过行数的任何字段都不会输出。
  • 对于任何记录,请跳过 opt 字段并增加 j
  • 使用$j 的当前字段构建一个flds 变量,或者如果它似乎缺少一个字段,则使用dflts 的一个空字段。
  • 打印出flds,其中包含额外的空字段,但没有跳过的字段。

这里是功能的细分

create_empty_field():

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

skip_flds():

  • fnum是记录字段号的参数,而name是一个局部变量
  • $fnum 中拉出name 部分
  • 使用fld_rule[name] == "skp" 测试检查是否应该跳过它。
  • 如果应该跳过它,增加 fnum 并重置 name 变量。
  • 我认为重复的name =sub 调用线应该是一个新功能,但我这里没有这样做。

基本上,我在known_flds 中制定解析/转换规则,然后使用awk.script 针对data 文件中的记录解释/强制执行它们。虽然这是一个合理的开始,但当强制性字段不存在或为空时,您还可以将错误打印到另一个文件,向字段添加缺少的子字段等。您可以随心所欲。

【讨论】:

  • 绝对是一个很好的起点,但我确实有一个问题(这可能是个愚蠢的问题)...fld_cnt47 来自哪里?
  • 好的。我现在明白了。如果我没看错,这不包括删除当前不在known_flds 中的字段。对吗?
  • 我刚刚发现客户端显然是在动态添加字段到每个段的末尾。根据我使用上述逻辑进行的测试,如果找到记录类型,它似乎不会强制字段数等于 known_flds 文件中的值(即使它看起来应该如此)。它只是在每个 ~ 值之间移动所有字段。有没有办法强制字段数量?
  • client(awk 脚本或数据文件创建者)是什么意思?
  • 数据文件创建者。提供文件的客户。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-29
  • 1970-01-01
  • 2018-10-20
  • 1970-01-01
  • 2021-09-17
  • 1970-01-01
相关资源
最近更新 更多