49

在 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”)?

4

9 回答 9

36

除了@Jerry 的awk 回答,还有其他选择:

使用cut(默认采用制表符分隔符):

cut -f32-58 foo >bar

使用perl

perl -nle '@a=split;print join "\t", @a[31..57]' foo >bar
于 2012-11-15T04:36:56.163 回答
28

轻度修改版:

BEGIN { s = 32; e = 57; }

      { for (i=s; i<=e; i++) printf("%s%s", $(i), i<e ? OFS : "\n"); }
于 2012-11-15T04:12:10.970 回答
8

您可以使用 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() 函数。对于其他 awk,您将使用 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 进行调整。

于 2012-11-15T05:17:21.987 回答
7

我迟到了,但这很快就说到点子上了,所以我把它留在这里。在这种情况下,我通常只使用 gsub 删除不需要的字段并打印。快速而肮脏的例子,因为您知道您的文件由制表符分隔,您可以删除前 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 周期。

于 2017-08-16T09:12:45.503 回答
2

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

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

但是,这看起来有点 hacky:

  • 它根据指定的正确分隔您的输出OFS,并且
  • 它确保在文件中的每个输入行的末尾打印一个新行。
于 2012-11-15T04:16:05.367 回答
1

我不知道在 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

于 2012-11-15T13:15:44.380 回答
1

不幸的是,似乎无法再访问我的帐户,但也没有 50 个代表来添加评论。

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

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

结合 Bob 的答案,我们得到:

    $ 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

    $
于 2019-05-22T14:11:47.770 回答
0

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

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

  return s
}
于 2017-07-06T10:26:33.990 回答
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

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

于 2018-01-31T10:32:20.463 回答