3

入力ファイルからN列ごとに抽出し、抽出順序に従って出力ファイルに書き込むためのコードを書くのに苦労してきました。

(私の実際のケースでは、列6から始まる合計24005列のファイルから800列ごとに抽出するため、ループが必要です)

以下のより単純なケースでは、2番目の列の開始点を持つ入力ファイルから3列(フィールド)ごとに抽出します。

たとえば、入力ファイルが次のようになっている場合:

aa 1 2 3 4 5 6 7 8 9 
bb 1 2 3 4 5 6 7 8 9 
cc 1 2 3 4 5 6 7 8 9 
dd 1 2 3 4 5 6 7 8 9 

そして、出力を次のようにしたいと思います。output_file_1:

1 2 3
1 2 3
1 2 3
1 2 3

output_file_2:

4 5 6  
4 5 6 
4 5 6 
4 5 6 

output_file_3:

7 8 9
7 8 9 
7 8 9
7 8 9

私はこれを試しましたが、機能しません:

awk 'for(i=2;i<=10;i+a) {{printf "%s ",$i};a=3}' <inputfile>

構文エラーが発生し、修正すればするほど問題が発生します。

Linuxコマンドカットも試しましたが、大きなファイルを処理している間、これは簡単に思えます。そして、cutはawkのように3フィールドごとにループカットを行うのだろうかと思います。

誰かがこれを手伝って、簡単な説明をしてくれませんか?前もって感謝します。

4

4 に答える 4

3

入力データに対して awk によって実行されるアクションは中かっこで囲む必要があるため、試した awk ワンライナーが構文エラーになる理由は、forサイクルがこの規則を尊重していないためです。構文的に正しいバージョンは次のようになります。

awk '{for(i=2;i<=10;i+a) {printf "%s ",$i};a=3}' <inputfile>

これは構文的には正しいですが (この投稿の最後を参照してください)、あなたが思っているようには動作しません。

異なるファイルの列ごとに出力を分離するには、awkリダイレクト演算子を使用するのが最善の方法です>。入力ファイルに常に 10 列があるとすると、これにより目的の出力が得られます。

awk '{ print $2,$3,$4 > "file_1"; print $5,$6,$7 > "file_2"; print $8,$9,$10 > "file_3"}' <inputfile>

" "ファイル名を指定することに注意してください。


編集済み:実世界のケース

列が多すぎるために列に沿ってループする必要がある場合でも、awk (gawk) を 2 つのループで使用できます。1 つは出力ファイルで、もう 1 つはファイルごとの列です。これは可能な方法です:

#!/usr/bin/gawk -f 

BEGIN{
  CTOT = 24005 # total number of columns, you can use NF as well
  DELTA = 800  # columns per file
  START = 6 # first useful column
  d = CTOT/DELTA # number of output files.
}
{
  for ( i = 0 ; i < d ; i++)
  {
    for ( j = 0 ; j < DELTA ; j++)
    {
      printf("%f\t",$(START+j+i*DELTA)) > "file_out_"i
    }
    printf("\n") >  "file_out_"i
   }
 }

あなたの例の単純な入力ファイルでこれを試しました。CTOT を DELTA で割ることができれば機能します。フロート ( %f) を必要なものに変更するだけだと思いました。

お知らせ下さい。


Ps は元のワンライナーに戻りますが、インクリメントされていないため、ループは無限ループであることに注意しii+aください。i+=aa=3

awk '{for(i=2;i<=10;i+=a) {printf "%s ",$i;a=3}}' <inputfile>

これは、サイクルごとに a=3 を評価しますが、これは少し無意味です。したがって、より良いバージョンは次のようになります。

awk '{for(i=2;i<=10;i+=3) {printf "%s ",$i}}' <inputfile>

それでも、これはファイルの 2 列目、5 列目、8 列目を印刷するだけであり、これは望んだものではありません。

于 2013-02-01T20:16:37.347 に答える
2
awk '{ print $2, $3,  $4 >"output_file_1";
       print $5, $6,  $7 >"output_file_2";
       print $8, $9, $10 >"output_file_3";
     }' input_file

これにより、入力ファイルの 1 つのパスが作成されます。これは、複数のパスよりも望ましい方法です。明らかに、示されているコードは固定数の列 (したがって固定数の出力ファイル) のみを処理します。必要に応じて、可変数の列を処理したり、可変ファイル名を生成したりするために変更できます。


(私の実際のケースでは、列 6 から始まる合計 24005 列のファイルから 800 列ごとに抽出するため、ループが必要です)

その場合、あなたは正しいです。ループが必要です。実際には、次の 2 つのループが必要です。

awk 'BEGIN { gap = 800; start = 6; filebase = "output_file_"; }
     {
         for (i = start; i < start + gap; i++)
         {
             file = sprintf("%s%d", filebase, i);
             for (j = i; j <= NF; j += gap)
                  printf("%s ", $j) > file;
             printf "\n" > file;
         }
     }' input_file

25 列 (対応する列の番号 1 ~ 25) の入力ファイルと、ギャップを 8 に設定し、開始を 2 に設定して、これを満足のいくように示しました。以下の出力は、結果として得られた 8 つのファイルを水平方向に貼り付けたものです。

2 10 18    3 11 19    4 12 20    5 13 21    6 14 22    7 15 23    8 16 24    9 17 25
2 10 18    3 11 19    4 12 20    5 13 21    6 14 22    7 15 23    8 16 24    9 17 25
2 10 18    3 11 19    4 12 20    5 13 21    6 14 22    7 15 23    8 16 24    9 17 25
2 10 18    3 11 19    4 12 20    5 13 21    6 14 22    7 15 23    8 16 24    9 17 25
于 2013-02-01T20:16:34.383 に答える
2

GNU awk の場合:

$ awk -v d=3 '{for(i=2;i<NF;i+=d) print gensub("(([^ ]+ +){" i-1 "})(([^ ]+( +|$)){" d "}).*","\\3",""); print "----"}' file
1 2 3
4 5 6
7 8 9
----
1 2 3
4 5 6
7 8 9
----
1 2 3
4 5 6
7 8 9
----
1 2 3
4 5 6
7 8 9
----

必要に応じて、出力をファイルにリダイレクトするだけです。

$ awk -v d=3 '{sfx=0; for(i=2;i<NF;i+=d) print gensub("(([^ ]+ +){" i-1 "})(([^ ]+( +|$)){" d "}).*","\\3","") > ("output_file_" ++sfx)}' file

アイデアは、gensub() に最初のいくつか (i-1) フィールドをスキップしてから、必要なフィールド数 (d = 3) を出力し、残り (.*) を無視するように指示することです。フィールド数の正確な倍数を印刷していない場合は、最後のループ反復で印刷されるフィールド数をマッサージする必要があります。計算する...

これは、どの awk でも動作するバージョンです。2 つのループが必要で、フィールド間のスペースを変更しますが、おそらく理解しやすいでしょう。

$ awk -v d=3 '{sfx=0; for(i=2;i<=NF;i+=d) {str=fs=""; for(j=i;j<i+d;j++) {str = str fs $j; fs=" "}; print str > ("output_file_" ++sfx)} }' file
于 2013-02-02T13:14:08.527 に答える
1

I was successful using the following command line. :) It uses a for loop and pipes the awk program into it's stdin using -f -. The awk program itself is created using bash variable math.

for i in 0 1 2; do 
    echo "{print \$$((i*3+2)) \" \" \$$((i*3+3)) \" \" \$$((i*3+4))}" \
  | awk -f -  t.file   > "file$((i+1))"
done

Update: After the question has updated I tried to hack a script that creates the requested 800-cols-awk script dynamically ( a version according to Jonathan Lefflers answer) and pipe that to awk. Although the scripts looks good (for me ) it produces an awk syntax error. The question is, is this too much for awk or am I missing something? Would really appreciate feedback!

Update: Investigated this and found documentation that says awk has a lot af restrictions. They told to use gawk in this situations. (GNU's awk implementation). I've done that. But still I'll get an syntax error. Still feedback appreciated!

#!/bin/bash

# Note! Although the script's output looks ok (for me)
# it produces an awk syntax error. is this just too much for awk?

# open pipe to stdin of awk
exec 3> >(gawk -f - test.file)

# verify output using cat
#exec 3> >(cat)

echo '{' >&3

# write dynamic script to awk
for i in {0..24005..800} ; do
    echo -n " print " >&3
    for (( j=$i; j <= $((i+800)); j++ )) ; do
        echo -n "\$$j " >&3
        if [ $j = 24005 ] ; then
            break
        fi
    done
    echo "> \"file$((i/800+1))\";" >&3
done
echo "}"
于 2013-02-01T20:09:46.683 に答える