我一直在努力编写一个代码,用于从输入文件中提取每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个字段进行循环切割。
有人可以帮我解决这个问题并快速解释一下吗?提前谢谢。
答案 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 "}"