【问题标题】:Awk to extract and format a highly variable text fileawk 提取和格式化高度可变的文本文件
【发布时间】:2015-11-22 23:06:46
【问题描述】:

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

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

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

我已删除所有标题,但将它们放回此处仅供参考。在类型 2 行中,缺少 Mechanic 字段。

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

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

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

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

我可以自己处理列标题和东西,我只是无法破解如何处理文件中的 FS 问题和可变列数的代码。我可以通过 grep 找出我需要的特定信息,但我会很高兴将其转换为可以将其导入电子表格或数据库的表单。

【问题讨论】:

  • 我们是来帮助您处理代码的,所以请提供一些您尝试过的代码。
  • 听起来你需要找到一些指标来告诉你一行是有11个字段还是10个字段,然后根据它过滤行,然后才能处理它们。考虑到数据的可变性,你最好用 Python 或 Perl 编写一些简单的东西来表达你想要做的事情并清理数据,而不是像 awk 这样的面向列的工具。
  • @merlin2011 听起来您从未使用过 awk。也许听到了谣言或看到了一些非常琐碎或非常糟糕的例子,但从未实际使用过。对于这样的问题,您在 Perl 或 Python 中可以做的任何事情都不能在 awk 中做。
  • @edmorton 我不是 awk 的重度用户。我通常在数据被清晰分隔时使用它,并在数据变得混乱时切换到 python。我也没有声称这在 awk 中是不可能的,只是不太自然。 :)
  • @merlin2011 你很高兴使用 python,这并没有什么问题,但是对于处理复杂或其他的文本,awk 与任何其他工具一样有效,通常会产生比大多数更清晰的代码它是每个 UNIX 安装的标准配置。如果您愿意,可以提倡使用 python 或 perl,但建议人们在某些方面会比 awk 更好地用于文本操作,这完全是错误的。如果你觉得 python 在这个问题上会比 awk 更好,那么请随时发布一个 python 解决方案,以便我们可以将它与我发布的 awk 进行比较。

标签: regex awk delimiter


【解决方案1】:

您的输入文件还不错。假设您的输入文件是用分号分隔的:

  1. $2 中的所有空白字符替换为; 以将其拆分为单独的字段以供输出,然后
  2. 如果$3 中有空格,则将第一个空格替换为;(因为它同时包含服务和描述,因此您需要将它们分开),否则
  3. 这是一种未指定机制的行格式,因此请在 $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

【讨论】:

  • 如果他接受这个答案,我会写一个 Python 答案,因为我不清楚他的例子是否充分抓住了他的问题的细微差别。这就是为什么我最初没有写答案的原因。另外,我很欣赏从这个答案中学到更多的 awk 。 :)
【解决方案2】:

我的一个朋友也给我发了这个解决方案,用 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;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-05-29
    • 1970-01-01
    • 1970-01-01
    • 2014-06-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多