Trích xuất các bản ghi chiều rộng cố định không có dấu phân cách từ một dòng


8

Tôi cần trích xuất các chuỗi văn bản từ một tệp duy nhất chứa một dòng văn bản rất dài không có dấu phân cách. Sử dụng dòng mẫu dưới đây, đây là những sự thật được biết đến sau đây:

??????? A1XXXXXXXXXX ??????? B1XXXX ??????? A1XXXXXXXXXX ????????

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

??????? C1XXXXXXXX

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.

Mã Perl được tái cấu trúc để đưa các cập nhật của bạn vào tài khoản. Xin vui lòng xem nếu nó giúp.
Joseph R.

Cảm ơn Joseph. Tôi không biết Perl nhưng muốn làm rõ rằng tệp chỉ chứa 1 dòng văn bản, tức là không có dòng trả lại hoặc ngắt dòng. Chỉ muốn làm rõ điều đó bởi vì tôi thấy trong các bình luận của bạn, bạn ngụ ý rằng tập tin có nhiều hơn 1 dòng trừ khi tôi nói tôi đã đọc sai điều này. Cảm ơn nhiều.
trò đùa

Điều này sẽ không làm cho một sự khác biệt. Mã Perl sẽ hoạt động như nhau nếu tất cả nằm trên một dòng hoặc nếu có một số, miễn là mỗi dòng chứa một số nguyên các bản ghi được tạo tốt.
Joseph R.

Cảm ơn bạn rất nhiều Joseph. Nó đã hoạt động. Đã thử nghiệm nếu một điểm đánh dấu trong cơ thể của hồ sơ và điều này quay trở lại vượt qua điều đó. Bất cứ ai có thể cung cấp một tương đương Unix xin vui lòng?
trò đùa

Hãy nhìn vào câu trả lời cập nhật của tôi.
Joseph R.

Câu trả lời:


5

Làm thế nào về

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

Điều này in từng bản ghi của từng loại bản ghi trên một dòng riêng biệt. Để chuyển hướng grepđầu ra cho 3 tập tin có tên A1, B1, C1tương ứng,

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

Cám ơn bạn rất nhiều về điều này. Bạn có phiền giải thích các thành phần tập lệnh và công tắc khác nhau được sử dụng để tôi có thể kiểm tra và mở rộng không. Ngoài ra, làm thế nào để tôi thêm mẫu của số 9 trước nó (trong thực tế sẽ là các ký tự chữ và số dài 7 ký tự). Cảm ơn nhiều.
trò đùa

Đã nói quá sớm ... Tôi cũng nên thêm 1 thông tin quan trọng đó là mẫu.recordmarker có thể xuất hiện trong phần còn lại của bản ghi để chúng tôi khuyên bạn nên loại bỏ một bản ghi tại một tệp và sao chép lại tệp có thể có nghĩa là tôi không thể sử dụng grep.
trò đùa

Hơn nữa, tôi có 2 giải pháp khả thi. - duyệt qua tệp, nhãn có ký tự tối nghĩa để biểu thị bắt đầu bản ghi hợp lệ. Di chuyển ký tự X tùy thuộc vào loại bản ghi và sử dụng cùng một ký tự tối nghĩa để biểu thị bản ghi tiếp theo. Tuy nhiên cảnh giác với bất kỳ vấn đề đệm. Do đó mong đợi đầu ra mới để thẩm vấn tìm kiếm như thế này "\\ \\ 9999999A1XXXXXXXXXX 9999999B1XXXX \\ \\ 9999999A1XXXXXXXXXX 9999999C1XXXXXXX???" - sử dụng sol hiện tại nhưng sau đó tìm kiếm trong mỗi tập tin đầu ra nếu các mô hình khác xuất hiện khác so với lúc đầu
jags

@jags, bạn có thể muốn cập nhật câu hỏi ban đầu của mình với dữ liệu mẫu thực sự đại diện, tất cả đều hơi khó hiểu
iruvar

Cảm ơn bạn 1_CR, tôi đã gửi lại câu hỏi. Cảm ơn mọi sự giúp đỡ của bạn. Cảm kích nhất.
trò đùa

4

Đây là một giải pháp rõ ràng bằng cách sử dụng FPAT của gawk

BEGIN { 
    FPAT="A1.{10}|B1.{4}|C1.{7}" #define field contents
} 
{
    for(i=1;i<=NF;i++) 
        print $i >> substr($i,0,2) #print the field to file A1,B1,etc
}

Như một lớp lót:

gawk 'BEGIN{FPAT="A1.{10}|B1.{4}|C1.{7}"} {for(i=1;i<=NF;i++)print $i >> substr($i,0,2)}' < datafile

Lưu ý rằng FPATyêu cầu phiên bản gawk 4. Xem: linuxjournaldigital.com/linuxjournal/201109#pg98
Håkon Hægland

4

Trong 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
}

Gọi nó là:

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

Mã được kiểm tra và hoạt động với đầu vào nhất định của bạn.

Cập nhật

Trong các bình luận của bạn, bạn đã yêu cầu một "tương đương Unix" ở trên. Tôi rất nghi ngờ có tồn tại một điều như vậy, vì biểu thức Perl được sử dụng để phân tích dòng của bạn là một biểu thức rất bất thường và tôi nghi ngờ rằng các biểu thức chính quy vanilla có thể phân tích định dạng dữ liệu đã cho của bạn: nó quá giống với một loại biểu thức nổi tiếng mà regex có thể phân tích cú pháp (khớp với bất kỳ số lượng nào ađược theo sau bởi cùng số lượng b).

Trong mọi trường hợp, cách tiếp cận "Unix" gần nhất tôi có thể tìm thấy là khái quát hóa câu trả lời của 1_CR . Bạn nên lưu ý rằng phương pháp này dành riêng cho việc triển khai GNU grepvà do đó sẽ không hoạt động trên hầu hết các Thông báo. Ngược lại, cách tiếp cận Perl sẽ hoạt động giống nhau trên mọi nền tảng mà Perl hoạt động. Đây là grepcách tiếp cận GNU được đề xuất của tôi :

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

Cập nhật

Dựa trên các yêu cầu của OP trong các bình luận, thay vì chuyển tên tệp dưới dạng đối số dòng lệnh, nó có thể được mở trong tập lệnh như sau:

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

Điều này giả sử bạn đã khai báo biến $input_file_namechứa, tên tệp đầu vào.

Đối với việc gắn dấu thời gian vào tên tệp đầu ra, bạn có thể sử dụng qx{}cú pháp: giữa các dấu ngoặc, bạn có thể đặt bất kỳ lệnh Unix nào bạn muốn và nó sẽ được chạy và đầu ra tiêu chuẩn của nó được đọc lại thay cho qx{}toán tử:

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

Các qxnhà điều hành không bị hạn chế để niềng răng, sử dụng nhân vật yêu thích của bạn như delimiter, chỉ cần đảm bảo nó không có trong lệnh mà bạn cần phải chạy:

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

và như thế...

Trong một số mã Perl, bạn có thể thấy backticks ( ` `) được sử dụng để phục vụ chức năng này thay vào đó, tương tự như trình bao. Chỉ cần nghĩ về qxtoán tử là sự khái quát hóa của backticks cho bất kỳ dấu phân cách nào.

Nhân tiện, điều này sẽ đưa ra dấu thời gian hơi khác nhau cho mỗi tệp (nếu sự khác biệt về thời gian tạo của chúng xảy ra là một số giây hữu hạn). Nếu bạn không muốn điều này, bạn có thể thực hiện theo hai bước:

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

Xin chào lần nữa .... bắt đầu thực sự yêu perl. Chỉ cần có một vài bit niggly. 1 . Làm thế nào để đọc trong tệp trái ngược với truyền trong đối số dòng lệnh. Đang thử nhưng không sử dụng cấu hình chạy Eclipse. 2 . Cách nối một số văn bản vào tệp tên tệp đầu ra $. Cảm kích nhất.
trò đùa

@jags Chào mừng đến với câu lạc bộ :). Trả lời cập nhật. Xem nếu nó giúp.
Joseph R.

Cảm ơn Joseph. Tuy nhiên, đối với yêu cầu cuối cùng tôi có nghĩa là thực sự nối thêm, ví dụ, ngày / dấu thời gian vào tên tệp đầu ra. Mã hiện tại xuất ra các tệp A1, B1 & C1. Rất cám ơn một lần nữa.
trò đùa

@jags tôi thấy. Xin vui lòng xem nếu cập nhật giúp.
Joseph R.

Cảm ơn như mọi khi Joseph. Tuy nhiên, tôi có nghĩa là nối vào tên tệp đầu ra thực tế mà trong trường hợp này hiện là A1, B1, C1, tức là tôi muốn thêm ngày / dấu thời gian, A1_ <today_date>, B1_ <today_date>, C1_ <today_date>. Cảm ơn nhiều.
trò đùa
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.