awk 提取和格式化高度可变的文本文件
Awk to extract and format a highly variable text file
我正在处理一个乱七八糟的文本文件。这是我购买的二手房车的服务记录,这是正则表达式爱好者的噩梦
它具有不一致的字段分隔符和不一致的字段数量,行是以下两种类型之一:
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 字段。
我用分号替换了所有出现的多个 space,所以我现在拥有的文件中有些行有 10 个字段,有些行有 11 个字段,有时字段分隔符是 space,在其他情况下它是分号,并且某些字段具有合法的嵌入 spaces(描述和机械)。
我正在尝试使用 awk 来:
1) 提取每个字段并能够用统一的OFS打印出来(最好是分号)
2) 如果缺少 Mechanic 字段,插入它并打印 N/A 或 -- 对于 Mechanic
我可以自己处理列标题和内容,我只是无法破解如何处理此文件中的 FS 问题和可变列数的代码。我可以 grep 出我需要的特定信息,但我会很高兴将其转换为可以将其导入电子表格或数据库的形式。
你的输入文件还不错。假设您的输入文件以分号分隔:
- 用
;
替换 </code> 中的所有空白字符,将其拆分为单独的字段进行输出,然后 </li>
<li>如果<code>
中有空格,则将第一个空格替换为;
(因为它同时包含服务和描述,所以您需要将它们分开),否则
- 这是一种没有指定机制的行格式,因此在
</code>(描述)</li> 之后添加空机制文本
</ol>
<p>然后只打印一行:</p>
<pre><code>$ awk 'BEGIN{FS=OFS=";"} {gsub(/ /,OFS,)} !sub(/ /,OFS,){= 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,) }
!sub(/ /,OFS,) { = OFS "N/A" }
{
[=11=] = [=11=]
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
我的一个朋友也给我发了这个解决方案,用 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;
我正在处理一个乱七八糟的文本文件。这是我购买的二手房车的服务记录,这是正则表达式爱好者的噩梦
它具有不一致的字段分隔符和不一致的字段数量,行是以下两种类型之一:
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 字段。
我用分号替换了所有出现的多个 space,所以我现在拥有的文件中有些行有 10 个字段,有些行有 11 个字段,有时字段分隔符是 space,在其他情况下它是分号,并且某些字段具有合法的嵌入 spaces(描述和机械)。
我正在尝试使用 awk 来:
1) 提取每个字段并能够用统一的OFS打印出来(最好是分号)
2) 如果缺少 Mechanic 字段,插入它并打印 N/A 或 -- 对于 Mechanic
我可以自己处理列标题和内容,我只是无法破解如何处理此文件中的 FS 问题和可变列数的代码。我可以 grep 出我需要的特定信息,但我会很高兴将其转换为可以将其导入电子表格或数据库的形式。
你的输入文件还不错。假设您的输入文件以分号分隔:
- 用
;
替换</code> 中的所有空白字符,将其拆分为单独的字段进行输出,然后 </li> <li>如果<code>
中有空格,则将第一个空格替换为;
(因为它同时包含服务和描述,所以您需要将它们分开),否则 - 这是一种没有指定机制的行格式,因此在
</code>(描述)</li> 之后添加空机制文本 </ol> <p>然后只打印一行:</p> <pre><code>$ awk 'BEGIN{FS=OFS=";"} {gsub(/ /,OFS,)} !sub(/ /,OFS,){= 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,) } !sub(/ /,OFS,) { = OFS "N/A" } { [=11=] = [=11=] 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
我的一个朋友也给我发了这个解决方案,用 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;