从一行中提取不带定界符的固定宽度记录


8

我需要从单个文件中提取文本字符串,该文件包含一行很长的文本,没有定界符。使用下面的示例行,这些是以下已知事实:

??????? A1XXXXXXXXXX ??????? B1XXXX ??????? A1XXXXXXXXXX ??????? C1XXXXXXX

1.  It contains 38 fixed width record types 
2.  The record marker is a 7 alphanumeric character followed by, for example, A1’.
3.  Each record type has varying widths, for example, A1 record type will have 10 characters following it, if B1 then 4, and if C1 then 7.
4.  The record types arent clumped together and can be in any order. As in the example, its A1,B1,A1,C1
5.  The example above has 4 records and each record type needs to go to separate files. In this case 38 of them.

??????? A1XXXXXXXXXX

??????? B1XXXX

??????? A1XXXXXXXXXX

??????? C1XXXXXXX

6.  The record identifier, e.g. ????????A1, can appear in the body of the record so cannot use grep. 
7.  With the last point in mind, I was proposing 3 solutions but not sure on how to script this and of course would greatly appreciate some help. 
a. Traverse through the file from the beginning and sequentially strip out the record to the appropriate output file. For example, strip out first record type A1 to A1file which I know is 10 characters long then re-interrogate the file which will then have B1 which I know is 4 chars long, strip this out to B1file etc.. <<< this seems painful >>
b. Traverse through the file and append some obscure character to each record marker within the same file. Much like above but not strip out. I understand it still will use the same logic but seems more elegant
c. I did think of simply using the proposed grep -oE solution but then re-interrogate the output files to see if any of the 38 record markers exist anywhere other than at the beginning. But this might not always work.

重构Perl代码以考虑您的更新。请查看是否有帮助。
Joseph R.

谢谢约瑟夫。我不了解Perl,但想明确指出该文件仅包含1行文本,即没有回车符或换行符。只是想说清楚一点,因为我在您的评论中看到您暗示文件有1行以上,除非像我说的那样我读错了。非常感谢。
jags 2013年

这应该没有什么不同。如果Perl代码全部在一行上或有几行,则Perl代码将相同,只要每一行包含整数个格式正确的记录即可。
Joseph R.

非常感谢约瑟夫。好了 测试了记录标记是否在记录主体中,并且这种向后引用可以克服这一点。任何人都可以提供Unix等效产品吗?
jags 2013年

请查看我更新的答案。
Joseph R.

Answers:


5

怎么样

grep -oE 'A1.{10}|B1.{4}|C1.{7}' input.txt

这会将每个记录类型的每个记录打印在单独的行上。为了重定向grep输出到一个名为3个文件A1B1C1分别

grep -oE 'A1.{10}|B1.{4}|C1.{7}' input.txt| 
awk -v OFS= -v FS= '{f=$1$2; $1=$2=""; print>f}'

非常感谢你做的这些。您介意解释这些使用的各种脚本组件和开关,以便于我进行测试和扩展。另外,我如何在它之前添加9的模式(实际上是7个字符长的字母数字字符)。非常感谢。
jags 2013年

发言太早...我还应该添加1条重要信息,那就是pattern.recordmarker可能会出现在记录的其余部分中,因此建议我们一次将一条记录剥离到一个文件中,然后重新审阅该文件表示我不能使用grep。
jags 2013年

此外,我有2种可能的解决方案。-遍历文件,并用模糊字符标记以表示有效记录的开始。根据记录类型移动X个字符,并使用相同的模糊字符表示下一条记录。但是要警惕任何缓冲区问题。因此期待新的输出询问这样看“\\ \\ 9999999A1XXXXXXXXXX \\ 9999999B1XXXX \\ 9999999A1XXXXXXXXXX 9999999C1XXXXXXX????” -使用电流溶胶但随后每个输出文件中进行搜索,如果其他的模式出现,比年初其他
尖齿

@jags,您可能想使用真正具有代表性的示例数据来更新您的原始问题,这一切都让人有些困惑
iruvar 2013年

谢谢1_CR,我已经重新提交了问题。谢谢大家的帮助。非常感谢。
jags


4

在Perl中:

#!/usr/bin/env perl

use strict;
use warnings;
use re qw(eval);

my %field_widths = (
    A1 => 10,
    B1 =>  4,
    C1 =>  7,
    #...(fill this up with the widths of your 38 record types)
);

# Make a regex of record types; sort with longest first as appropriate for
# ... regex alternation:
my $record_type_regex = join '|', sort { length($b) <=> length($a) } keys %field_widths; 

my %records;
my $marker_length=7; #Assuming the marker is 7 characters long
while(<>){
    chomp;
    while( # Parse each line of input
      m!
        (.{$marker_length})          # Match the record marker (save in $1)
        ($record_type_regex)         # Match any record type (save in $2)
        (
         (??{'.'x$field_widths{$2})} # Match a field of correct width
        )                            # Save in $3
       !xg){
        $records{$2}.="$1$2$3\n";
      }
}
for my $file (sort keys %records){
    open my $OUT,'>',$file or die "Failed to open $file for writing: $!\n";
    print $OUT $records{$file};
    close $OUT
}

调用为:

[user@host]$ ./myscript.pl file_of_data

测试了代码,并可以使用给定的输入。

更新资料

在您的评论中,您要求上述内容与“ Unix等效”。我非常怀疑是否存在这样的事情,因为用于解析您的行的Perl表达式是一个高度不规则的表达式,并且我怀疑香草正则表达式能否解析您的给定数据格式:它与正则表达式可以处理的著名表达式类型太相似't解析(匹配任意数量的,a后跟相同数量b的)。

无论如何,我能找到的最接近的“ Unix”方法是1_CR的答案的推广。您应该注意,这种方法特定于GNU的GNU实现,grep因此不适用于大多数Unices。相反,Perl方法应该在Perl所使用的任何平台上都可以使用。这是我建议的GNU grep方法:

cat <<EOF \
| while read -r record width;do
    grep -oE ".{7}$record.{$width}" input_file\ #replace 7 with marker length
     >> "$record"
done
A1 10
B1 4
# enter your 38 record types
EOF

更新资料

根据OP在注释中的请求,可以像在脚本中那样打开文件名,而不必将文件名作为命令行参数传递:

open my $IN,'<',$input_file_name or die "Failed to open $input_file: $!\n";
while(<$IN>){ #instead of while(<>)
...

假设您已声明变量$input_file_name包含输入文件名。

至于将时间戳附加到输出文件名,您可以使用以下qx{}语法:在花括号之间可以放置所需的任何Unix命令,它将运行,并且其标准输出将代替qx{}运算符:

open my $OUT,'>',"$file_".qx{date +%Y-%m-%d--%I:%M:%S%P}

qx运营商不局限于括号,用你最喜欢的角色作为分隔符,只要确定它不是在你需要运行下面的命令:

qx<...>
qx(...)    
qx!...!    
qx@...@

等等...

在某些Perl代码中,您可能会看到反引号(` `)代替了此功能,与shell相似。只需将qx运算符视为对任何定界符的反引号的推广即可。

顺便说一句,这将为每个文件提供稍微不同的时间戳(如果它们的创建时间之差恰好是有限的秒数)。如果您不希望这样做,可以分两个步骤进行:

my $tstamp = qx{...};
open my $OUT,'>',"$file_$tstamp" or die...;

再次嗨....开始真正爱perl。只是有几个小问题。1。如何在文件中读取而不是在命令行参数中传递。尝试但无法使用Eclipse运行配置。2。如何在输出文件名$ file中附加一些文本。非常感谢。
jags 2013年

@jags欢迎来到俱乐部:)。答案已更新。看看是否有帮助。
Joseph R.

谢谢约瑟夫。但是对于最后一个请求,我实际上打算将例如日期/时间戳附加到输出文件名。当前代码输出文件A1,B1和C1。再次非常感谢。
jags 2013年

我明白了 请查看更新是否有帮助。
Joseph R.

一如既往地感谢约瑟夫。但是我的意思是附加到实际输出文件名,在这种情况下,当前文件名当前为A1,B1,C1,即我想添加日期/时间戳,A1_ <todays_date>,B1_ <todays_date>,C1_ <todays_date>。非常感谢。
jags 2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.