如何提取每N列并写入新文件?

时间:2013-02-01 19:43:53

标签: linux awk

我一直在努力编写一个代码,用于从输入文件中提取每N列,并根据它们的提取顺序将它们写入输出文件。

(我的真实情况是从第6列开始从总共24005列文件中提取每800列,所以我需要一个循环)

在下面的简单案例中,从输入文件中提取每3列(字段)的第2列的起点。

例如,如果输入文件如下所示:

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命令,但是当我处理大文件时,这似乎毫不费力。我想知道切割是否会像awk一样对每3个字段进行循环切割。

有人可以帮我解决这个问题并快速解释一下吗?提前谢谢。

4 个答案:

答案 0 :(得分: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),有两个循环:一个在输出文件上,一个在每个文件的列上。这是一种可能的方式:

#!/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)只是根据你的需要改变它。

让我知道。


P.S。回到原来的单行,注意循环是无限的,因为i没有递增:i+a必须由i+=a代替,a=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列,这不是您想要的。

答案 1 :(得分: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

这使得一次传递输入文件,这比多次传递更可取。显然,所显示的代码仅处理固定数量的列(因此是固定数量的输出文件)。如有必要,可以修改它以处理可变数量的列并生成变量文件名等。


  

(我的真实情况是从第6列开始从总共24005列文件中提取每800列,所以我需要一个循环)

在那种情况下,你是对的;你需要一个循环。实际上,您需要两个循环:

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

答案 2 :(得分: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

答案 3 :(得分:1)

我使用以下命令行成功了。 :)它使用for循环并使用-f -将awk程序输入到它的stdin中。 awk程序本身是使用bash变量数学创建的。

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

更新:问题更新后,我试图破解一个脚本,动态创建所请求的800-cols-awk脚本(根据Jonathan Lefflers的答案回答)并将其传递给awk。尽管脚本看起来很好(对我来说),但它会产生awk语法错误。问题是,这对于awk来说太多了还是我错过了什么?真的很感激反馈!

更新:对此进行了调查,发现documentation表示awk有很多限制。他们告诉他们在这种情况下使用gawk。 (GNU的awk实现)。我做到了。但是我仍然会遇到语法错误。仍然反馈赞赏!

#!/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 "}"