【问题标题】:How to extract every N columns and write into new files?如何提取每 N 列并写​​入新文件?
【发布时间】:2013-02-01 19:43:53
【问题描述】:

我一直在努力编写代码,用于从输入文件中提取每 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 

我希望输出看起来像这样: 输出文件_1:

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

输出文件_2:

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

输出文件_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,但是当我处理大文件时,这似乎毫不费力。而且我想知道 cut 是否会像 awk 一样对每 3 个字段执行一次循环剪切。

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

【问题讨论】:

    标签: linux awk


    【解决方案1】:

    awk 对输入数据执行的操作必须包含在花括号中,因此您尝试的 awk 单行代码导致语法错误的原因是 for 循环不遵守此规则。语法正确的版本是:

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

    这在语法上是正确的(几乎,请参阅本文结尾。),但并没有按照您的想法进行。

    要按不同文件上的列分隔输出,最好使用awk 重定向运算符&gt;。鉴于您的输入文件始终有 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),只需根据需要更改即可。

    告诉我。


    附:回到你原来的单行,注意循环是一个无限循环,因为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 列,这不是您想要的。

    【讨论】:

      【解决方案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
      

      【讨论】:

      • 将学习用 awk 编写不止一个衬里!再次感谢!
      • 有一点需要注意 - 对于 800 个文件,您可能会遇到进程可以同时打开的文件描述符数量的问题。如果不能打开 800 多个文件,awk 有机会处理它;有可能不会。如果它确实处​​理了它,那么每个文件的每一行输出都将涉及关闭和打开一个文件,这很昂贵。因此,可能值得对真实数据进行一些时序测试(就线宽而言),但要进行小规模测试(就数据行数而言)。查看trussstracedtrace 或等效项的输出。
      【解决方案3】:

      使用 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
      

      【讨论】:

        【解决方案4】:

        我成功使用了以下命令行。 :) 它使用 for 循环并使用 -f - 将 awk 程序通过管道传输到它的标准输入中。 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 来说是不是太多了,还是我错过了什么?非常感谢反馈!

        更新:对此进行了调查,发现documentationawk 有很多限制。他们告诉在这种情况下使用 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 "}"
        

        【讨论】:

        • 我喜欢你的方法,但问题是在现实世界中,我的问题更像是在总共 24000 列文件中获取每 800 列。据我了解,脚本“for i in 0 1 2; do”的第一行是定义要提取的列数(我在这里正确吗?),所以放三个数字就可以了,但是有没有更简单的方法我可以代替将所有 800 个数字放在这里吗?谢谢
        猜你喜欢
        • 2019-06-30
        • 1970-01-01
        • 2012-06-15
        • 1970-01-01
        • 2016-05-26
        • 1970-01-01
        • 2021-05-05
        • 2018-08-28
        • 1970-01-01
        相关资源
        最近更新 更多