awkスクリプトの各行に対してFIELDWIDTHSを変更できますか?

awkスクリプトの各行に対してFIELDWIDTHSを変更できますか?

私が受け取ったファイルには、データセットを表す行が含まれています。各行には、複数のデータフィールドを表す文字が区別なく含まれています。各データフィールドを抽出するには、行を破棄する必要があります。

ABCD075BCD156300544E0001000900125349544520494

->

ABCD 075BCD 15630 0544 E00010009 00 12 5349544520494

各フィールドの文字数がわかっているので、FIELDWIDTHSを使用してawkでこれを行うことができます。このアプローチは、すべてのデータセットが同一である場合に有効ですが、そうではありません。問題をより複雑にすることは、フィールドの1つを読み取ると、それがどのタイプのデータセットであるかを知ることができるということです。

したがって、データ型を読み取るには、FIELDWIDTHSセットを適用する必要があるようです。次に、別のFIELDWIDTHSセットを適用し、getlineを使用して同じ行を再読み込みしてデータを抽出します。ただし、変更されたフィールド幅が適用されないため、これは機能しません。

BEGIN {
  FIELDWIDTHS = "30 2";                  # set FIELDWIDTHS to read data type
}

{
  print $2;                              # print data type
  FIELDWIDTHS = "5 5 5 5 5 5 5 5 5 5 5"; # change fieldwidths to read data
  getline NF;                            # reread current line to use new fieldwidths
  print $2;                              # print data field
  FIELDWIDTHS = "30 2";                  # change fieldwidths to read next line
}

END {
}

どんなアドバイスも本当にありがとうございます。

答え1

何でもアッ:

awk 'NR%2   { fieldwidths="4 6 5 4 9 2 2 13" } # update fieldwidths on odd line numbers
    !(NR%2) { fieldwidths="4 5 4 2 3 9 7 11" } # update fieldwidths on even line numbers
    # condition { fieldwidths="# # #  ..." }   # whatever other condition you want...

{ fields=split(fieldwidths, fldwd); startPos=1;
  for(i=1; i<=fields; i++) {
      printf "%s", (i==1?"": OFS) substr($0, startPos, fldwd[i])
      startPos+=fldwd[i]
  }
  print ""
}' infile

答え2

私は次のようなもの(FIELDWIDTHSにGNU awkを使用すること)があなたが望むものだと思います:

BEGIN {
    type2fw[10] = "7 3 6 8 9"
    type2fw[12] = "5 5 5 5 5 5 5 5 5 5 5"
    type2fw[53] = "1 1 1 17 29 31"
    ....
}
{
    FIELDWIDTHS = type2fw[substr($0,31,2)]
    $0 = $0
    do whatever you like with the fields
}

ただし、これはフィールド分割を2回実行するため(レコードを読むときに1回、$ 0 = $ 0を実行するときに2回目)、少し非効率的です。タイプが変更された場合にのみ再分割して効率を向上させることができます。

BEGIN {
    type2fw[10] = "7 3 6 8 9"
    type2fw[12] = "5 5 5 5 5 5 5 5 5 5 5"
    type2fw[53] = "1 1 1 17 29 31"
    ....
}
{ type = substr($0,31,2) }
type != prev {
    FIELDWIDTHS = type2fw[type]
    $0 = $0
    prev = type
}
{
    do whatever you like with the fields
}

各タイプに対してFIELDWIDTHSを一度だけ変更することで、最初に31番目/32番目の文字タイプフィールド(たとえば)でsort -k1.31,1.32 file | awk '...'入力をソートできます。

入力と期待される出力の簡潔でテスト可能な複数行/タイプの例を見なければ、これより具体的ではないかもしれません。これは間違ったアプローチかもしれませんmatch($0,/(foo)(bar)(etc)/,a)

答え3

gnu awkを使用すると$0 = $0。例えば、

echo '1 abcdefghij
2   abcdefghij' |
awk '
/^1/{ FIELDWIDTHS = "1 1 5 5"; $0 = $0; print $3; next }
/^2/{ FIELDWIDTHS = "1 3 3 3"; $0 = $0; print $3; next }
'

あるいは、処理された行をプレフィックス文字として表示するなど、1つのフィールド形式を処理するawkを介してデータを送信し、#結果を2番目のawkにパイプするUnixと同様のソリューションを検討することもできます。例えば、

awk -v FIELDWIDTHS="1 1 5 5" '
/^1/{ print "#" $3; next }
    { print }
' |
awk -v FIELDWIDTHS="1 3 3 3" '
/^2/{ print $3; next }
/^#/{ print substr($0,2); next }
    { print }
'

答え4

別の使用方法GNU sed/eコマンドの修飾子はここに表示されます。s///

一般的なアイデアは、現在の入力レコードの31から32文字の名前を持つファイルにスペースで区切られたフィールド幅のリストを保存することです。これPKキャンセル現在のレコードに関連するフィールド幅を保持するファイル名を関数に指定します。次に、これらの幅に基づいて現在のレコードを分割するsedコードを生成します。

#--- edit this function to add the fieldwidths corresponding to
#--- the 2 characters in the 31st/32nd
#--- positions of the input record
_init_() {
  [ -s "$1" ] && return
  case $1 in
    */12) echo '4 6 5 4 9 2 2 13' ;;
    */96) echo '5 5 5 6 7 2 2 13' ;;
  esac > "$1"
}

_unpk_() {
  _init_ "$1"
< "$1" tr -s ' \t' '[\n*]' |
sed -Ee '
  1i\
$!d;H;z;x
  s|.*|s/\\n.{&}/\&\\n/|
  s|$|;s/\\n/ /|
  $a\
s/^.|.$//g
'
}

export -f _init_ _unpk_
tmpdir=$(mktemp -d)

sed -Ee "w $tmpdir/h
  s:.{30}(..).*:_unpk_ '$tmpdir/\\1' | sed -Ef - '$tmpdir/h':e
" file

関連情報