1行から区切り文字なしの固定幅レコードを抽出する


8

区切り文字のない非常に長い1行のテキストを含む単一のファイルからテキストの文字列を抽出する必要があります。以下のサンプル行を使用すると、これらは以下の既知の事実です。

??????? 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コードがリファクタリングされました。それが役立つかどうか見てください。
ジョセフR.

ジョセフありがとう。Perlは知りませんが、ファイルに1行のテキストしか含まれていないこと、つまり、改行や改行がないことを明確にしたいと思いました。私があなたがこれを誤って読んだと言ったのでない限り、あなたがあなたのコメントでファイルが1行以上あることを示唆しているので、それを明確にしたかっただけです。どうもありがとう。
ジャグ

これは違いを生むべきではありません。Perlコードは、各行に整数の整形式レコードが含まれている限り、すべてが1行にある場合でも、複数行ある場合でも同じように機能します。
ジョセフR.

ジョセフ、どうもありがとうございました。それは働いた。レコードマーカーがレコードの本文にあり、この後方参照がそれを克服するかどうかでテストされました。誰でもUnixの同等物を提供できますか?
ジャグ

私の更新された答えを見てください。
Joseph R.

回答:


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文字の英数字です)。どうもありがとう。
ジャグ

スポークが早すぎます... pattern.recordmarkerがレコードの残りの部分に表示される可能性があるという重要な情報も1つ追加する必要があったので、一度にレコードをファイルから削除し、おそらくファイルに再度問い合わせます。 grepを使用できないことを意味します。
ジャグ

さらに、2つの解決策があります。-ファイルをトラバースし、有効なレコードの開始を示す不明瞭な文字でラベルを付けます。レコードタイプに応じてX文字を移動し、同じ文字を使用して次のレコードを示します。ただし、バッファの問題には注意してください。したがって、「?\\ 9999999A1XXXXXXXXXX?\\ 9999999B1XXXX?\\ 9999999A1XXXXXXX?\\ 9999999C1XXXXXXX」のように、新しい出力が問い合わせを行うことが期待されます-現在のsolを使用しますが、他のパターンが最初以外に表示される場合は、各出力ファイル内を検索します
ジャグ

@jags、元の質問を本当に代表的なサンプルデータで更新したい場合があります。すべて混乱します
iruvar

1_CRに感謝します。質問を再送信しました。ご協力ありがとうございます。最も感謝しています。
ジャグ

4

これがgawkのFPATを使用した可能な解決策です

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
}

ワンライナーとして:

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

FPAT:gawkのバージョン4を参照してください必要がlinuxjournaldigital.com/linuxjournal/201109#pg98
ホーコンHægland

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実装に固有であるため、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コードでは` `、シェルと同じように、代わりにこの関数を提供するために使用されるバックティック()が表示される場合があります。qx演算子を、区切り記号へのバックティックの一般化と考えてください。

ちなみに、これは各ファイルにわずかに異なるタイムスタンプを与えます(作成時間の差がたまたま有限の秒数である場合)。これが不要な場合は、次の2つのステップで実行できます。

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

こんにちは....本当にPerlを愛し始めています。ほんのわずかなビットを持っているだけです。。コマンドライン引数で渡すのではなく、ファイルを読み込む方法。Eclipse実行構成を使用しようとしましたが、失敗しました。。出力ファイル名$ fileにテキストを追加する方法。最も感謝しています。
ジャグ

@jagsクラブへようこそ:)。回答を更新しました。それが役立つかどうかを確認します。
Joseph R.

ジョセフに感謝します。ただし、最後のリクエストでは、たとえば出力ファイル名に日付/タイムスタンプを実際に追加するつもりでした。現在のコードは、ファイルA1、B1およびC1を出力します。改めて感謝いたします。
ジャグ

@jagsわかりました。アップデートが役立つかどうかを確認してください。
ジョセフR.13年

いつものようにジョセフに感謝します。ただし、この場合は現在A1、B1、C1である実際の出力ファイル名に追加するつもりでした。つまり、日付/タイムスタンプ、A1_ <todays_date>、B1_ <todays_date>、C1_ <todays_date>を追加します。どうもありがとう。
ジャグ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.