如何使用 awk 打印表中包含两个字符串之一的所有字段

如何使用 awk 打印表中包含两个字符串之一的所有字段

我有一个包含许多行和每行可变数量的列的表。

在每一行中,我只想打印第一个字段以及包含两个字符串之一的所有字段(在本例中,我想要包含单词狗和牛的所有字段)。

例如:

A   dog999   dog284   cow284   pig383   pig234   cow432   chicken432
B   cow394   cow432   cow345   dog983   pig345   chicken532 
C   dog847   pig357   pig236   cow395   dog496
D   dog392   cow237   cow749

期望的输出:

A   dog999   dog284   cow284   cow432   
B   cow394   cow432   cow345   dog983   
C   dog847   cow395   dog496
D   dog392   cow237   cow749

到目前为止,我已经使用 awk:

awk -v OFS='\t' '{for (i = 1; i <= NF; i++) {if ($i ~ /dog/) print $1,$i; else if ($i ~ /cow/) print $1,$i} }' file.txt

但这会导致包含这两个字符串之一的每个字段都占一行。

答案1

如果perl解决方案没问题:

$ cat ip.txt 
A   dog999   dog284   cow284   pig383   pig234   cow432   chicken432
B   cow394   cow432   cow345   dog983   pig345   chicken532 
C   dog847   pig357   pig236   cow395   dog496
D   dog392   cow237   cow749

$ perl -lane 'print join("\t",$F[0],grep {/cow|dog/} @F[1..$#F])' ip.txt 
A   dog999  dog284  cow284  cow432
B   cow394  cow432  cow345  dog983
C   dog847  cow395  dog496
D   dog392  cow237  cow749
  • -a按空格分割输入行并保存到@F数组
  • -l从输入中删除换行符并在打印时添加回来
  • join\t打印时会在元素之间添加一个
  • $F[0],grep {/cow|dog/} @F[1..$#F]数组的第一个元素和所有匹配cow或的元素dog
  • 也可以使用perl -lape'$_=join"\t",shift(@F),grep/cow|dog/,@F'.这里shift将删除并返回@F数组的第一个元素,将结果分配给$_将在末尾打印礼貌-p选项(提示斯蒂芬·查泽拉斯


如果不包含cow或的行dog被忽略:

perl -lane 'print join("\t",$F[0],grep {//} @F[1..$#F]) if /cow|dog/' ip.txt 

答案2

你很接近,但你需要提取第一个值,因为你不想为每个匹配的单词打印它。我们可以使用printf它来避免换行。

awk '{printf "%s",$1
      for (i=1;i<=NF;i++)
      {
        if ($i ~ /dog|cow/) { printf " %s",$i; }
      }
      print ""
     }'

输出将是:

A dog999 dog284 cow284 cow432
B cow394 cow432 cow345 dog983
C dog847 cow395 dog496
D dog392 cow237 cow749

这可以折叠为一行:

awk '{printf "%s",$1; for (i=1;i<=NF;i++) { if ($i ~ /dog|cow/) { printf " %s",$i; }  } print ""  }'

请注意,这将打印一行与任何单词都不匹配的行,例如

E pig sheep

将输出

E

答案3

TXR awk 宏

$ txr -e '(awk (:let tmp)
               (:begin (set ofs "\t"))                     
               (f (set tmp (pop f))
                  (ff (keep-if #/cow|dog/))
                  (push tmp f) (prn)))' data
A   dog999  dog284  cow284  cow432
B   cow394  cow432  cow345  dog983
C   dog847  cow395  dog496
D   dog392  cow237  cow749

分解:

  1. :let宏中的子句指定局部变量。该宏实现了“Awk Paradigm”,但采用类型安全语言,其中变量必须在使用前定义。因此,除了像:beginand之类的子句:end(类似于POSIX Awk 中的BEGINand END)之外,此 Awk 还提供:let定义词法作用域为宏的变量。

  2. (f (set tmp (pop f)) ...)是一个条件-动作子句,其中条件是f。如果是记录中分隔字段的列表;如果它不为空(不等于nil),则其行为类似于布尔 true。因此,如果 中有任何内容,操作表单就会执行f

  3. (set tmp (pop f))从列表中弹出第一个字段并将其保存在临时变量中tmp。第二个字段成为第一个,第三个字段成为第二个,依此类推。当我们对 进行操作时f,记录rec也会使用 来自动重构ofs,就像在 POSIX Awk 中一样,使用字段之间$0来重构记录。OFS

  4. (ff ...)通过操作过滤字段,在本例中为(keep-if #/regex/)。基本上我们从f所有与正则表达式不匹配的字段中删除。ff是宏内部可见的运算符awkkeep-if是一个正则函数;这里它是隐式柯里化的,所以列表参数不会出现。它需要一个谓词函数,但正则表达式是函数可调用的,因此适合作为谓词。

  5. 然后我们将之前保存的第一个字段推回到字段列表f(push tmp f)

  6. (prn)相当于print.如果不带参数,它会打印记录,后跟输出记录分隔符 ( ors),该分隔符初始化为换行符。由于rec在 的所有操作之后已被重构f,我们得到了过滤后的输出。

可以看出,Awk 范式基本上是完整的,只是在不同语言的上下文中,不同种类的事情是可能的。仅仅能够在不检查这些字段是否实际存在的情况下进行操作的便利性$2 > $1并不存在;但另一方面,我们不必编写循环来将字段作为数据结构进行处理。字段可以通过函数映射或视为堆栈。

Sundeep 的 Perl 解决方案大致翻译为awk这样的宏:

$ txr -e '(awk (t (prn `@[f 0]\t@{(keep-if #/cow|dog/ [f 1..:]) "\t"}`)))' data

相关内容