
私はfile.txt.Z
これを含むこれを持っています:
AK2*856*1036~AK3*TD1*4**~AK4*2**1*~AK4*7**1*~AK3*TD5*5**~AK4*3**6*2~AK3*REF*6**~AK4*2**1*~AK3*REF*7**~AK4*2**1*~AK3*REF*8**~AK4*2**1*~AK3*DTM*9**~AK4*2**4*20~AK4*2**4*20~AK3*CTT*12**7~AK5*R
AK2*856*1037~AK3*HL*92**~AK4*3**7*O~AK5*R~AK9*R*2*2*0~SE*25*0001~GE*1*211582~IEA*1*000211582
各レコードは、AK
ヘッダー(通常は数字を含む)で区切られた複数のフィールドで構成されています~
。インデントされた改行文字に置き換えると、~
次のようになります。
AK2*856*1036
AK3*TD1*4**
AK4*2**1*
AK4*7**1*
AK3*TD5*5**
AK4*3**6*2
AK3*REF*6**
AK4*2**1*
AK3*REF*7**
AK4*2**1*
AK3*REF*8**
AK4*2**1*
AK3*DTM*9**
AK4*2**4*20
AK4*2**4*20
AK3*CTT*12**7
AK5*R
AK2*856*1037
AK3*HL*92**
AK4*3**7*O
AK5*R
AK9*R*2*2*0
SE*25*0001
GE*1*211582
IEA*1*000211582
各フィールドには、で区切られたサブフィールドがあります*
。たとえば、サブフィールドAK201
はヘッダーの後の最初のフィールドなので、サンプル行に使用されますAK2
。856
ご覧のとおり、開始文字列は2行ですAK2
。これは、行ヘッダーまたは段落ヘッダーと呼ばれるものと同じです。その中には2つの段落がありますfile.txt.Z
。私が望むのは、各セグメントヘッダーからこのデータを順番に取得することです。
必要なデータ:
- AK202(
AK2
ヘッダーの後の2番目のフィールド) -AK2*856*this_numeric_value
アスタリスクの前または~
。 - AK301(
AK3
ヘッダーの後の最初のフィールド) - 前または~AK3*this_string_value
前。*
~
- AK502(ヘッダーの後ろの2番目のフィールド
AK5
) - 前または~AK5*some_string_value*this_numeric_value
前。*
~
- AK401(
AK4
ヘッダーの後の最初のフィールド) - 前または~AK4*this_numeric_value
前です。*
~
AK4
フィールドの各数値はAK5
常に2桁以上でなければなりません。例えば、AK502 = 2、AK502 = 02、またはAK401 = 9。- フィールドがなければ
AK3
何も出力されません。 (すでにスクリプトがあります) - 1行に複数のAK3-AK5-AK4シーケンスが含まれている場合は、スペースで連結する必要があります。
AK5
このフィールドの後にフィールドが欠落している場合は、代わりにAK3
そのAK4
フィールドを見つけてください。- そのフィールド
AK4
とそれに続くフィールドの両方がない場合、AK301(AK3ヘッダーの後の最初のフィールド)のみが出力されます。AK5
AK3
AK4
フィールドの後に複数のフィールドがある場合は、AK3
AK502-AK401シーケンスをコンマで連結してください。
出力:
GS: 1036 - TD102,07 TD503 REF02 DTM02,02 CTT
GS: 1037 - HL03
どうすればいいですか?私の質問が混乱している場合は、私に尋ねてください。
編集する:これは私のコードです。これはwhileループ内にあります。
while read FILE
do
AK2=`zgrep -oP 'AK2.[\w\s\d]*.\K[\w\s\d]*' < $FILE`
AK3=`zgrep -oP 'AK3.\K[\w\s\d]*' < $FILE`
AK5=`zgrep -oP 'AK5.[\w\s\d]*.\K[\w\s\d]' < $FILE`
AK5_ERROR=`if [[ $AK5 =~ ^[0-9]+$ ]]; then printf "%02d" $AK5 2> /dev/null; else 2> /dev/null; fi`
AK4=`zgrep -oP 'AK4.\K[\w\s\d]*' < $FILE`
AK4_ERROR=`if [[ $AK4 =~ ^[0-9]+$ ]]; then printf "%02d" $AK4 2> /dev/null; else 2> /dev/null; fi`
if [[ $AK3 ]]
then
if $AK5 2> /dev/null
then
echo "GS: $AK2 - $AK3$AK4_ERROR"
else
echo "GS: $AK2 - $AK3$AK5_ERROR"
fi
else
echo "Errors are not specified in the file."
fi
done < file.txt.Z
私の元のコードの問題は、$AK3
and、$AK5
またはが接続されていないことです$AK4
。
答え1
次のPerlスクリプトは、サンプル入力が提供されたときに正確にサンプル出力を生成します。
実際のデータファイルでは、必要に応じて正しく機能しない可能性がありますが、完全な作業ソリューションでは提供されません。これは、ジョブを開始する基礎として使用されます。スクリプトを使って遊んで、めちゃくちゃにして、壊して、修正し、好きなように変更してください。
間違いなく最適とは離れていますが、入力データと目的の出力のより詳細な理解/より良い解釈なしには大幅に改善することは困難です。
これは、各入力行(「レコード」とも呼ばれるか、「セグメント」という用語を使用)を処理し、レコードを処理した後に印刷する文字列を作成します。各出力ラインはあなたの仕様に合わせて作られています。必要なデータあなたの質問の一部です。
#!/usr/bin/perl
use strict;
while(<>) {
next unless /AK3/; # skip lines that don't contain AK3
# process each "segment" aka "record".
my @fields = split /~/;
# get segment "header" and 2nd sub-field of that header.
my @segment = split(/\*/,$fields[0]);
my $segment_header = $segment[2];
shift @fields;
my $output = "GS: $segment_header -";
my $groupoutput = ''; # output for a given AK3 "group"
my $last_go = ''; # used to avoid duplicates like "REF02 REF02 REF02"
foreach my $f (@fields) {
my @subfields = split /\*/,$f;
if ($f =~ m/^AK3/) {
if (($groupoutput) && ($groupoutput ne $last_go)) {
$output .= " $groupoutput";
$last_go = $groupoutput; # remember the most recent $groupoutput
};
$groupoutput = $subfields[1];
} elsif ($f =~ m/^AK4/) {
my $ak401 = $subfields[1];
$groupoutput .= sprintf("%02i,",$ak401) if ($ak401 > 0);
} elsif ($f =~ m/^AK5/) {
my $ak502 = $subfields[2];
$groupoutput .= sprintf("%02i",$ak502) if ($ak502 > 0);
};
};
# append the group output generated since the last seen AK3 (if any)
# i.e. don't forget to print the final group on the line.
$output .= " $groupoutput" if (($groupoutput) && ($groupoutput ne $last_go));
# clean up output string before printing.
$output =~ s/, / /g;
$output =~ s/\s*$|,$//;
print $output, "\n";
}
mysteryprocess.pl
より適切な名前がわからないので、このスクリプトをとして保存しました。その後、サンプルデータ(というファイルにありますinput
)を使用して実行しました。
出力例:
$ ./mysteryprocess.pl input
GS: 1036 - TD102,07 TD503 REF02 DTM02,02 CTT
GS: 1037 - HL03
「REF02 REF03 REF02」問題が私を悩ませているので、ここに別のバージョンがあります。これは、配列とハッシュ(および@groups
)%groups
を使用して出力行を作成し、別のハッシュ(%gseen
)を使用してすでにレポート出力に含まれている値を記憶し、レコードの重複を防ぎます。
グループデータはに保存されますが、%groups
ハッシュの順序は指定されていないため、特定のグループを最初に見た順序を記憶するために配列が使用されますperl
。@groups
しかし、%groups
おそらくHoA(つまり、各要素を持つ配列のハッシュ)と呼ばれる配列ハッシュが必要です。これにより$output
、印刷する前にクリーンアップする必要はありません(join()
単にコンマと新しい値を追加する代わりにPerlの機能を使用するだけです)。文字列)。しかし、私はこのスクリプトがPerl初心者にとって十分に複雑であると思います。
#!/usr/bin/perl
use strict;
while(<>) {
next unless /AK3/; # skip lines that don't contain AK3
# process each "segment" aka "record".
my @fields = split /~/;
# get segment "header" from 1st field, and then 2nd sub-field of that header.
# NOTE: "shift" returns the first field of an array AND removes it from
# the array.
my @segment = split(/\*/, shift @fields);
my $segment_header = $segment[2];
my $output = "GS: $segment_header -";
my @groups=(); # array to hold each group name (ak301) in the order that
# we see them
my %groups=(); # hash to hold the ak401/ak502 values for each group
my %gseen =(); # used to avoid dupes by holding specific values of ak301+ak401
# and ak301+ak502 that we've seen before.
my $ak301='';
foreach my $f (@fields) {
my @subfields = split /\*/, $f;
if ($f =~ m/^AK3/) {
$ak301 = $subfields[1];
if (!defined($groups{$ak301})) {
push @groups, $ak301;
};
} elsif ($f =~ m/^AK4/) {
my $ak401 = sprintf("%02i",$subfields[1]);
$ak401 = '' if ($ak401 == 0);
next if ($gseen{$ak301.'ak4'.$ak401});
if (!defined($groups{$ak301})) {
$groups{$ak301} = $ak401;
} else {
$groups{$ak301} .= ',' . $ak401;
};
$gseen{$ak301.'ak4'.$ak401}++;
} elsif ($f =~ m/^AK5/) {
my $ak502 = sprintf("%02i",$subfields[1]);
$ak502 = '' if ($ak502 == 0);
next if ($gseen{$ak301.'ak5'.$ak502});
if (!defined($groups{$ak301})) {
$groups{$ak301} = $ak502;
} else {
$groups{$ak301} .= ',' . $ak502;
};
$gseen{$ak301.'ak5'.$ak502}++;
};
};
# construct the output string in the order we first saw each group
foreach my $group (@groups) {
$output .= " $group" . $groups{$group};
};
# clean up output string before printing.
$output =~ s/, | +/ /g;
$output =~ s/\s*$|,$//;
print $output, "\n";
}
入力方法
AK2*856*1036~AK3*TD1*4**~AK4*2**1*~AK4*7**1*~AK3*TD5*5**~AK4*3**6*2~AK3*REF*6**~AK4*2**1*~AK3*REF*7**~AK4*2**1*~AK3*REF*8**~AK4*2**1*~AK3*DTM*9**~AK4*2**4*20~AK4*2**4*20~AK3*CTT*12**7~AK5*R
AK2*856*1037~AK3*HL*92**~AK4*3**7*O~AK5*R~AK9*R*2*2*0~SE*25*0001~GE*1*211582~IEA*1*000211582
AK2*856*1099~AK3*TD1*4**~AK4*2**1*~AK4*7**1*~AK3*TD5*5**~AK4*3**6*2~AK3*REF*6**~AK4*2**1*~AK3*REF*7**~AK4*2**1*~AK3*REF*8**~AK4*3**1*~AK3*REF*8**~AK4*2**1*~AK3*DTM*9**~AK4*2**4*20~AK4*2**4*20~AK3*CTT*12**7~AK5*R
これで出力は次のようになります。
$ ./mysteryprocess.pl input
GS: 1036 - TD102,07 TD503 REF02 DTM02 CTT
GS: 1037 - HL03
GS: 1099 - TD102,07 TD503 REF02,03 DTM02 CTT
メモ:
DTM02,02
また、ちょうど縮小しましたDTM02
。今、すべてが削除されます。- グループマージ(たとえば、同じAK301「名前」を持つ要素)は、要素がレコードに表示される場所に関係なく発生します。以前のバージョンのみがマージされました。近いフィールド/サブフィールドが同じ場合。
これらの変更があなたが望むものかどうかはわかりません。
PS:まだインストールしていない場合、perl
このコードはとても簡単ですawk
。
答え2
別の方法は、CASが提案したようにawkバージョンを表示することです。もう少し微妙にすることもできましたが、とにかく学習経験でした。
#!/usr/bin/awk -f
function get_slice(elem, fc, tmpArr) {
split(elem, tmpArr, "*")
return tmpArr[fc]
}
BEGIN { FS="~" }
/AK2/ {
res = get_slice($1, 3) " - "
tmpStr = ""
# only continue with this line if there are any AK3 fields.
# otherwise may as well skip whole thing.
if (match($0, /AK3/)) {
loc=2
for (loc=2; loc<=NF; loc++)
if ($loc ~ /AK3/) break
for ( ; loc<=NF; loc++) {
if ($loc ~ /AK3/) {
# check to see whether the previous loop generated a duplicate
# tmpStr will be "" the first time
if (index(res, tmpStr) == 0)
res = res " " tmpStr
tmpStr = get_slice($loc, 2)
# c is a count of how many fields have been added after AK3.
# once positive, "," will be added.
c = 0
}
# add the other fields
else if ($loc ~ /AK4/) {
if ((s = get_slice($loc, 2)) != "")
tmpStr = tmpStr sprintf("%s%02d", c++ ? "," : "", s)
} else if ($loc ~ /AK5/) {
if ((s = get_slice($loc, 3)) != "")
tmpStr = tmpStr sprintf("%s%02d", c++ ? "," : "", s)
}
}
# this is repeated at the end, to make sure the final set is printed.
if (index(res, tmpStr) == 0)
res = res " " tmpStr
print res
}
}
最初に「~」にフィールドを分割し、各行に対して使用可能なすべてのフィールドを繰り返します。フィールドが必須の場合にのみ、必須要素を取得するために「*」のサブフィールドに分割されます。何も見つからない場合、「get_slice」は「」を返すので、これを確認する必要があります。
問題を理解したようです。