3

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

输出文件_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 个字段进行一次循环剪切。

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

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

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

于 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

这使得一次通过输入文件,这比多次通过更可取。显然,显示的代码只处理固定数量的列(因此也处理固定数量的输出文件)。如有必要,可以对其进行修改,以处理可变列数和生成可变文件名等。


(我的真实案例是从第 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
于 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

我成功使用了以下命令行。:) 它使用 for 循环并使用-f -. 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 来说是不是太多了,还是我错过了什么?非常感谢反馈!

更新:对此进行调查,发现文档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 "}"
于 2013-02-01T20:09:46.683 回答