Mengekstraksi catatan lebar tetap tanpa pembatas dari satu baris

8

Saya perlu mengekstrak string teks dari satu file yang berisi satu baris teks yang sangat panjang tanpa pembatas. Menggunakan contoh baris di bawah ini, ini adalah fakta yang diketahui berikut:

??????? 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.
jags
sumber
Kode Perl refactored untuk mengambil pembaruan Anda ke akun. Silakan lihat apakah itu membantu.
Joseph R.
Joseph terima kasih. Saya tidak tahu Perl tetapi ingin menjelaskan bahwa file tersebut hanya berisi 1 baris teks, yaitu tidak ada carriage return atau line break. Hanya ingin memperjelas karena saya melihat dalam komentar Anda Anda menyiratkan file memiliki lebih dari 1 baris kecuali seperti saya katakan saya sudah salah baca ini. Terimakasih banyak.
jags
Ini seharusnya tidak membuat perbedaan. Kode Perl akan bekerja sama jika semuanya dalam satu baris atau jika ada beberapa, asalkan setiap baris berisi angka integer dari rekaman yang terbentuk dengan baik.
Joseph R.
Terima kasih banyak, Joseph. Itu berhasil. Diuji dengan apakah penanda catatan ada dalam catatan dan referensi ini mengatasi itu. Adakah yang bisa menawarkan yang setara dengan Unix?
jags
Silakan lihat jawaban saya yang diperbarui.
Joseph R.

Jawaban:

5

Bagaimana tentang

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

Ini mencetak setiap catatan dari setiap jenis catatan pada baris yang terpisah. Untuk mengarahkan grepoutput ke 3 file bernama A1, B1, C1masing-masing,

grep -oE 'A1.{10}|B1.{4}|C1.{7}' input.txt| 
awk -v OFS= -v FS= '{f=$1$2; $1=$2=""; print>f}'
iruvar
sumber
Terima kasih banyak atas ini. Apakah Anda keberatan menjelaskan berbagai komponen skrip dan sakelar yang digunakan sehingga saya dapat menguji dan memperluas. Juga bagaimana cara menambahkan pola 9s sebelumnya (yang pada kenyataannya akan menjadi karakter alfanumerik sepanjang 7 karakter). Terimakasih banyak.
jags
Berbicara terlalu cepat ... Saya seharusnya juga menambahkan 1 informasi penting yaitu bahwa pattern.recordmarker mungkin muncul di sisa catatan sehingga disarankan untuk menghapus catatan pada satu waktu ke file dan menginterogasi ulang file yang mungkin berarti saya tidak bisa menggunakan grep.
jags
Selanjutnya, saya punya 2 solusi yang mungkin. - melintasi file, beri label dengan karakter yang tidak jelas untuk menunjukkan awal dari catatan yang valid. Memindahkan karakter X tergantung pada tipe catatan dan menggunakan karakter yang tidak jelas yang sama untuk menunjukkan catatan berikutnya. Namun waspada terhadap masalah penyangga. Oleh karena itu mengharapkan keluaran baru untuk menginterogasi tampak seperti ini "\\ 9999999A1XXXXXXXXXX \\ 9999999B1XXXX \\ 9999999A1XXXXXXXXXX \\ 9999999C1XXXXXXX????" - penggunaan sol saat ini tetapi kemudian mencari di dalam setiap file output jika pola lain muncul selain di awal
jags
@ jags, Anda mungkin ingin memperbarui pertanyaan awal Anda dengan data sampel yang benar-benar representatif, semuanya menjadi sedikit membingungkan
iruvar
Terima kasih 1_CR, saya telah mengirimkan kembali pertanyaannya. Terima kasih atas bantuannya. Paling diapresiasi.
jags
4

Berikut adalah solusi yang memungkinkan menggunakan FPAT 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
}

Sebagai one-liner:

gawk 'BEGIN{FPAT="A1.{10}|B1.{4}|C1.{7}"} {for(i=1;i<=NF;i++)print $i >> substr($i,0,2)}' < datafile
rzymek
sumber
Catatan yang FPATmembutuhkan versi gawk 4. Lihat: linuxjournaldigital.com/linuxjournal/201109#pg98
Håkon Hægland
4

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

Ajukan sebagai:

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

Kode diuji dan berfungsi dengan input yang Anda berikan.

Memperbarui

Dalam komentar Anda, Anda meminta "Unix equivalent" di atas. Saya sangat meragukan ada hal seperti itu, karena ekspresi Perl yang digunakan untuk menguraikan baris Anda adalah ekspresi yang sangat tidak teratur dan saya ragu bahwa ekspresi reguler vanila dapat mem-parsing format data yang Anda berikan: itu terlalu mirip dengan jenis ekspresi yang terkenal yang dapat regex dapat parse (cocok dengan angka berapa pun adiikuti dengan angka yang sama b).

Bagaimanapun, pendekatan "Unix" terdekat yang dapat saya temukan adalah generalisasi jawaban 1_CR . Anda harus mencatat bahwa pendekatan ini khusus untuk implementasi GNU grepdan karenanya tidak akan berfungsi pada sebagian besar Unix. Pendekatan Perl, sebaliknya, harus bekerja sama pada platform apa pun yang bekerja pada Perl. Inilah greppendekatan GNU yang saya sarankan :

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

Memperbarui

Berdasarkan permintaan OP di komentar, alih-alih meneruskan nama file sebagai argumen baris perintah, itu dapat dibuka di dalam skrip seperti:

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

Ini mengasumsikan Anda telah mendeklarasikan variabel $input_file_nameberisi, yah, nama file input.

Sedangkan untuk menambahkan stempel waktu ke nama file output, Anda dapat menggunakan qx{}sintaks: antara kawat gigi Anda dapat menempatkan perintah Unix apa pun yang Anda inginkan dan itu akan dijalankan dan output standarnya dibaca kembali di tempat qx{}operator:

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

The qxoperator tidak terbatas pada kawat gigi, menggunakan karakter favorit Anda sebagai pembatas, hanya pastikan itu bukan dalam perintah yang Anda butuhkan untuk menjalankan:

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

dan seterusnya...

Dalam beberapa kode Perl Anda mungkin melihat backticks ( ` `) digunakan untuk melayani fungsi ini sebagai gantinya, mirip dengan apa yang dilakukan shell. Anggap saja qxoperator sebagai generalisasi dari backticks ke pembatas mana pun.

Ngomong-ngomong, ini akan memberikan stempel waktu yang sedikit berbeda untuk setiap file (jika perbedaan waktu pembuatannya adalah jumlah detik yang terbatas). Jika Anda tidak menginginkan ini, Anda dapat melakukannya dengan dua langkah:

my $tstamp = qx{...};
open my $OUT,'>',"$file_$tstamp" or die...;
Joseph R.
sumber
Hai lagi .... mulai sangat perl perl. Hanya memiliki beberapa bit niggly. 1 . Cara membaca di file sebagai lawan lulus dalam argumen baris perintah. Mencoba tetapi gagal menggunakan konfigurasi menjalankan Eclipse. 2 . Cara menambahkan beberapa teks ke file $ nama file output. Paling diapresiasi.
jags
@ jags Selamat datang di klub :). Jawaban diperbarui. Lihat apakah itu membantu.
Joseph R.
Terima kasih Joseph. Namun untuk permintaan terakhir saya bermaksud untuk benar-benar menambahkan, misalnya, tanggal / timestamp ke nama file keluaran. Kode saat ini menampilkan file A1, B1 & C1. Terima kasih banyak lagi.
jags
@Jags saya mengerti. Silakan lihat apakah pembaruan membantu.
Joseph R.
Terima kasih seperti biasa Joseph. Namun saya bermaksud menambahkan ke nama file keluaran aktual yang dalam hal ini adalah A1, B1, C1, yaitu saya ingin menambahkan tanggal / timestamp, A1_ <todays_date>, B1_ <todays_date>, C1_ <todays_date>. Terimakasih banyak.
jags