awk提取并格式化高度可变的文本文件

时间:2015-11-22 23:06:47

标签: regex awk delimiter

我正在处理一个混乱的文本文件。这是我正在购买的二手房车的服务记录,而且它是正则表达爱好者的噩梦

它具有不一致的字段分隔符和不一致的字段数,其中的行是以下两种类型之一:

Type 1 (11 columns):
UNIT   Mile  GnHr  R.O. Ln  Service  Description                Mechanic   Hours  $ Amt
7-9918;57878 1698 01633 021;0502-00C ENG OIL/ FILTERT IF NEEDED;M02 JOSE A. SANCHEZ;0.80;80.00

Type 2 (10 columns)
UNIT   Mile  GnHr  R.O. Ln  Service  Description   Hours  $ Amt
7-9918;55007 1641 [9564 007;ELE-BAT-BAT-0-0AAA;BATTERY AAA ALL BRANDS;2;31.12

我已经删除了所有标题,但是把它们放回这里仅供参考。在Type 2行中,缺少Mechanic字段。

我用分号替换了所有出现的多个空格,所以我现在所拥有的是一个文件,其中一些行有10个字段,有些行有11个字段,有时字段分隔符是空格,而在其他情况下它是分号,并且某些字段具有合法的嵌入空间(描述和机制)。

我正在尝试用awk找到一种方法:

1)提取每个字段并使用统一的OFS打印出来(首选分号)

2)如果缺少Mechanic字段,请插入并打印N / A或 - 为Mechanic

我可以自己处理列标题和内容,我只是无法破解如何处理此文件中的FS问题和可变数量的列的代码。我可以提供我需要的具体信息,但是很高兴能将它变成一个表格,我可以将其导入电子表格或数据库。

2 个答案:

答案 0 :(得分:1)

您的输入文件并不是那么糟糕。假设您的输入文件以分号分隔:

  1. $2中的所有空白字符替换为;,将其拆分为单独的字段以进行输出,然后
  2. 如果$3中有空白,则用;替换第一个空白(因为它包含服务和描述,因此您需要将它们分开),否则
  3. 这是一种没有指定机制的行格式,因此在$4之后添加空机制文本(说明)
  4. 然后只打印一行:

    $ awk 'BEGIN{FS=OFS=";"} {gsub(/ /,OFS,$2)} !sub(/ /,OFS,$3){$4=$4 OFS "N/A"} 1' file
    7-9918;57878;1698;01633;021;0502-00C;ENG OIL/ FILTERT IF NEEDED;M02 JOSE A. SANCHEZ;0.80;80.00
    7-9918;55007;1641;[9564;007;ELE-BAT-BAT-0-0AAA;BATTERY AAA ALL BRANDS;N/A;2;31.12
    

    如果您想对各个字段做任何事情:

    $ cat tst.awk
    BEGIN { FS=OFS=";" }
    { gsub(/ /,OFS,$2) }
    !sub(/ /,OFS,$3) { $4 = $4 OFS "N/A" }
    {
        $0 = $0
        print
        for (i=1; i<=NF; i++) {
            print NR, i, $i
        }
        print ""
    }
    

    $ awk -f tst.awk file
    7-9918;57878;1698;01633;021;0502-00C;ENG OIL/ FILTERT IF NEEDED;M02 JOSE A. SANCHEZ;0.80;80.00
    1;1;7-9918
    1;2;57878
    1;3;1698
    1;4;01633
    1;5;021
    1;6;0502-00C
    1;7;ENG OIL/ FILTERT IF NEEDED
    1;8;M02 JOSE A. SANCHEZ
    1;9;0.80
    1;10;80.00
    
    7-9918;55007;1641;[9564;007;ELE-BAT-BAT-0-0AAA;BATTERY AAA ALL BRANDS;N/A;2;31.12
    2;1;7-9918
    2;2;55007
    2;3;1641
    2;4;[9564
    2;5;007
    2;6;ELE-BAT-BAT-0-0AAA
    2;7;BATTERY AAA ALL BRANDS
    2;8;N/A
    2;9;2
    2;10;31.12
    

答案 1 :(得分:0)

我的一位朋友也给我发了这个解决方案,用perl完成:

#!/usr/bin/env perl -w

use strict;
use warnings;

#                                                                                                     1         1         1         1         1
#           1         2         3         4         5         6         7         8         9         0         1         2         3         4
# 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
# Type 1:
# 7-9918  55007 1641 [9564 022            0211     INTERIOR MISC.                  M02 JOSE A. SANCHEZ                0.00       0.00
# Type 2:
# 7-9918  57878 1698 01633 001            FUE-LPG-LPG-S-GAS      PROPANE GAS BULK PURCHASE                             5        24.00

my $delim="\t";

while (<STDIN>) {
    #print $_;

    # Both formats are the same at this point
    print substr($_, 0, 6) . $delim;
    print substr($_, 8, 5) . $delim;
    print substr($_, 14, 4) . $delim;
    print substr($_, 19, 5) . $delim;
    print substr($_, 25, 3) . $delim;

    my $qty = substr($_, 109, 11);
    $qty =~ s/^\s*//g;
    $qty =~ s/\s*$//g;

    if ($qty =~ /^\d+\.\d{2}$/) {
        # Type 1
        print substr($_, 40, 9) . $delim;
        print substr($_, 49, 32) . $delim;
        # print substr($_, 81, 32) . $delim; # Technician name
        print $qty . $delim;
    } elsif ($qty =~ /^[-]?\d+$/) {
        # Type 2
        print substr($_, 40, 23) . $delim;
        print substr($_, 63, 46) . $delim;
        print $qty . $delim;
    }
    print sprintf("%.2f", substr($_, 120, 11)) . "\n";
}

1;