我有一个包含许多行和每行可变数量的列的表。
在每一行中,我只想打印第一个字段以及包含两个字符串之一的所有字段(在本例中,我想要包含单词狗和牛的所有字段)。
例如:
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 -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
分解:
:let
宏中的子句指定局部变量。该宏实现了“Awk Paradigm”,但采用类型安全语言,其中变量必须在使用前定义。因此,除了像:begin
and之类的子句:end
(类似于POSIX Awk 中的BEGIN
andEND
)之外,此 Awk 还提供:let
定义词法作用域为宏的变量。(f (set tmp (pop f)) ...)
是一个条件-动作子句,其中条件是f
。如果是记录中分隔字段的列表;如果它不为空(不等于nil
),则其行为类似于布尔 true。因此,如果 中有任何内容,操作表单就会执行f
。(set tmp (pop f))
从列表中弹出第一个字段并将其保存在临时变量中tmp
。第二个字段成为第一个,第三个字段成为第二个,依此类推。当我们对 进行操作时f
,记录rec
也会使用 来自动重构ofs
,就像在 POSIX Awk 中一样,使用字段之间$0
来重构记录。OFS
(ff ...)
通过操作过滤字段,在本例中为(keep-if #/regex/)
。基本上我们从f
所有与正则表达式不匹配的字段中删除。ff
是宏内部可见的运算符awk
。keep-if
是一个正则函数;这里它是隐式柯里化的,所以列表参数不会出现。它需要一个谓词函数,但正则表达式是函数可调用的,因此适合作为谓词。然后我们将之前保存的第一个字段推回到字段列表
f
中(push tmp f)
。(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