在AWK中,是否可以指定字段的“范围”?

时间:2012-11-15 04:04:36

标签: awk

在AWK中,是否可以指定字段的“范围”?

实施例。给定一个制表符分隔的文件“foo”,每行100个字段,我想只打印每行的字段32到57,并将结果保存在文件“bar”中。我现在做什么:

awk 'BEGIN{OFS="\t"}{print $32, $33, $34, $35, $36, $37, $38, $39, $40, $41, $42, $43, $44, $45, $46, $47, $48, $49, $50, $51, $52, $53, $54, $55, $56, $57}' foo > bar

这样做的问题是键入并且容易出错是很乏味的。

是否有一些句法形式允许我以更简洁,更不容易出错的方式(如“$ 32 .. $ 57”)说同样的内容?

9 个答案:

答案 0 :(得分:30)

除了@Jerry的awk answer之外,还有其他选择:

使用cut(默认情况下假设制表符分隔符):

cut -f32-58 foo >bar

使用perl

perl -nle '@a=split;print join "\t", @a[31..57]' foo >bar

答案 1 :(得分:26)

轻度修订版:

BEGIN { s = 32; e = 57; }

      { for (i=s; i<=e; i++) printf("%s%s", $(i), i<e ? OFS : "\n"); }

答案 2 :(得分:6)

您可以使用RE间隔在awk中执行此操作。例如,要打印此文件中记录的字段3-6:

$ cat file
1 2 3 4 5 6 7 8 9
a b c d e f g h i

将是:

$ gawk 'BEGIN{f="([^ ]+ )"} {print gensub("("f"{2})("f"{4}).*","\\3","")}' file
3 4 5 6
c d e f

我正在创建一个RE段f来表示每个字段加上它的后续字段分隔符(为方便起见),然后我在gensub中使用它来删除其中的2个(即前2个字段),记住下一个4以后使用\ 3进行参考,然后删除它们之后的内容。对于您要打印字段32-57(即前31个字段后面的26个字段)的制表符分隔文件,您将使用:

gawk 'BEGIN{f="([^\t]+\t)"} {print gensub("("f"{31})("f"{26}).*","\\3","")}' file

以上使用GNU awk作为gensub()函数。使用其他awks,您可以使用sub()或match()和substr()。

编辑:以下是如何编写一个函数来完成这项工作:

gawk '
function subflds(s,e,   f) {
   f="([^" FS "]+" FS ")"
   return gensub( "(" f "{" s-1 "})(" f "{" e-s+1 "}).*","\\3","")
}
{ print subflds(3,6) }
' file
3 4 5 6
c d e f

只需根据需要设置FS。请注意,如果您的输入文件可以以空格开头和/或字段之间有多个空格,则只需要对默认FS进行调整,并且只有在您的FS是单个字符时才能使用。

答案 3 :(得分:3)

我迟到了,但这很快就到了,所以我会把它留在这里。在这种情况下,我通常只需用gsub和print删除我不需要的字段。快速而肮脏的示例,因为您知道文件由制表符分隔,您可以删除前31个字段:

awk '{gsub(/^(\w\t){31}/,"");print}'

删除4个字段的示例,因为懒惰:

printf "a\tb\tc\td\te\tf\n" | awk '{gsub(/^(\w\t){4}/,"");print}'

输出:

e   f

写入更短,更容易记忆,并且比可怕的循环使用更少的CPU周期。

答案 4 :(得分:2)

您可以在awk中使用循环和printf的组合:

#!/bin/bash

start_field=32
end_field=58

awk -v start=$start_field -v end=$end_field 'BEGIN{OFS="\t"}
{for (i=start; i<=end; i++) {
    printf "%s" $i;
    if (i < end) {
        printf "%s", OFS;
    } else {
        printf "\n";
    }
}}'

然而,这看起来有点黑客:

  • 根据指定的OFS
  • 正确分隔您的输出
  • 确保在文件的每个输入行末尾打印一个新行。

答案 5 :(得分:0)

我不知道在awk中进行字段范围选择的方法。我知道如何在输入结束时删除字段(参见下文),但在开头不容易。贝娄,一开始很难放弃田地。

如果您知道输入中未包含的字符c,则可以使用以下awk脚本:

BEGIN { s = 32; e = 57; c = "#"; }
{ NF = e            # Drop the fields after e.
  $s = c $s         # Put a c in front of the s field.
  sub(".*"c, "")    # Drop the chars before c.
  print             # Print the edited line.
}

修改

我只是觉得你总能找到一个不在输入中的角色:使用\n

答案 6 :(得分:0)

我使用这个简单的函数,它不会检查行中是否存在字段范围。

function subby(f,l, s) {
  s = $f
  for(i=f+1;i<=l;i++)
    s = sprintf("%s %s",s,$i)

  return s
}

答案 7 :(得分:0)

(我知道OP要求“在AWK中”但是......)

在命令行上使用bash扩展来生成参数列表;

$ cat awk.txt

1 2 3 4 5 6 7 8 9

a b c d e f g h i

$ awk "{print $(c="" ;for i in {3..7}; do c=$c\$$i, ; done ; c=${c%%,} ; echo $c ;)}" awk.txt

3 4 5 6 7
c d e f g

解释;

c="" # var to hold args list
for i in {3..7} # the required variable range 3 - 7
do 
   # replace c's value with concatenation of existing value, literal $, i value and a comma
   c=$c\$$i, 
done 
c=${c%%,} # remove trailing/final comma
echo $c #return the list string

使用分号放在单行上,在$()内进行评估/扩展。

答案 8 :(得分:0)

很不幸,似乎不再可以访问我的帐户,但也没有50名代表添加评论。

使用'seq'可以大大简化Bob的答案:

echo $(seq -s ,\$ 5 9| cut -d, -f2-)
$6,$7,$8,$9

次要缺点是您必须将第一个字段号指定为低一号。 因此,要获取3到7字段,我将2作为第一个参数。

seq -s,\ $ 2 7 将seq的字段分隔符设置为',$'并产生 2,$ 3,$ 4,$ 5,$ 6,$ 7

cut -d,-f2-将字段定界符设置为',',并通过显示第二个字段之后的内容来基本切掉第一个逗号之前的所有内容。因此产生 $ 3,$ 4,$ 5,$ 6,$ 7

结合鲍勃的答案,我们得到:

    $ cat awk.txt

    1 2 3 4 5 6 7 8 9

    a b c d e f g h i

    $ awk "{print $(seq -s ,\$ 2 7| cut -d, -f2-)}" awk.txt

    3 4 5 6 7

    c d e f g

    $