打印一段文本后退出AWK语句

时间:2019-06-28 06:59:37

标签: bash macos awk scripting text-manipulation

我的问题是我有一个非常大的数据库(10GB),我想节省尽可能多的时间来搜索它。我有一个awk语句正在数据库中搜索,并根据模式将数据写入另一个文件中。
我有一个输入文件,它将作为终端参数变量输入到我的脚本中。其中有几行数据将用作awk语句的模式。
在数据库中,所有与模式匹配的行都彼此相邻排序,因此,基本上,在打印后,由于已经找到了所有内容,因此无需进一步搜索数据库。 awk找到第一条模式匹配行后,所有其他模式匹配行将依次位于其后。

这个问题很难用单词来解释,因此我创建了一些示例,说明我的文件,代码和数据库的外观和操作方式。

通过终端输入的文件如下:

group_1
group_2
group_3
...

10GB的数据库如下所示:

group_1 DATA ...
group_1 DATA ...
group_1 DATA ...
group_2 DATA ...
group_2 DATA ...
group_2 DATA ...
group_2 DATA ...
group_3 DATA ...
group_3 DATA ...
group_3 DATA ...
group_3 DATA ...
...

带有问题awk的脚本代码如下:

IFS=$'\n'
set -f
for var in $(cat < "$1")
do  
    awk -v seq="$var" '{if (match($1, seq)) {print $0}}' filepath/database  > pattern_matched.file
done

对此代码的作用的简要解释是,它接受了Terminal参数变量(在这种情况下为文件名),并将其打开以供for loop开始循环。例如,将模式group_1放在var中,然后开始通过数据库进行搜索。如果第一列与模式匹配,它将把该行保存到文件pattern_matched.file文件中。

当前,它会搜索整个10GB的数据,然后按预期将数据打印到文件中,但是却浪费了很多时间。在打印出与模式匹配的行之后,我想停止awk继续通过数据库进行搜索,并继续从输入文件中移至下一个模式。 group_2的示例行为是awk检查数据库的前3行,并发现没有一行具有匹配的模式。但是,第4行包含该模式,因此它将在其后打印该行以及后续的模式匹配行。 awk到达第8行时,它退出awk语句,然后for loop可以迭代到下一个要搜索的模式group_3

awk '{print $0; exit}' filename

像这样的事情是行不通的,因为它只打印第一个实例并中断,我想要可以打印所有匹配项的东西,一旦找到下一个非模式匹配项,它就会中断。

先谢谢了。

更新: 现在的问题是,下面给出的解决方案在逻辑上是有意义的。如果输入if语句,它将把该行打印到文件中并迭代到下一行。如果该行不匹配,它将进入else-if语句并退出awk。这对我来说很有意义,但是由于某种原因,一旦flag变量已由第一条匹配行的if语句设置为1,它将进入else-if语句。由于else-if条件的值为true,因此它甚至在扫描下一行之前就退出了。我在awk语句中到处都用print语句确认了此行为。 这是我的带有打印语句的代码:

awk -v seq="$seqid" '{if(match($1, seq)) {print "matched" ; print $1 ; flag=1} else if (flag) {print "not matched" ; exit}}'

输出以下内容: weird behavior

4 个答案:

答案 0 :(得分:1)

您不能只将输入文件(input_file)读入awk:

$ cat input_file
group_1
group_3

Awk脚本:

$ awk 'NR==FNR{a[$0];next} $1 in a' input_file database
group_1 DATA ...
group_1 DATA ...
group_1 DATA ...
group_3 DATA ...
group_3 DATA ...
group_3 DATA ...
group_3 DATA ...

答案 1 :(得分:1)

您的外壳代码:

for var in $(cat < "$1")
do  
    awk 'script' filepath/database  > pattern_matched.file
done

使用反模式来读取存储在$1中的输入文件,请参见http://mywiki.wooledge.org/BashFAQ/001,并且将在循环的每次迭代中覆盖pattern_matched.file。我怀疑您应该将其写为:

while IFS= read -r var
do  
    awk 'script' filepath/database  
done < "$1" > pattern_matched.file

您的awk代码:

awk -v seq="$var" '{if (match($1, seq)) {print $0}}'

不必要地使用match(),因为您只想进行正则表达式比较,而没有使用match()填充的变量来帮助您隔离匹配的字符串(RSTART / RLENGTH),而是使用了无效的null条件,然后将实际条件放在操作空间中,然后硬编码打印当前记录的默认操作。等同于:

awk -v seq="$var" '$1 ~ seq'

但是我不认为您实际上需要进行正则表达式比较-给您的示例您应该进行字符串比较:

awk -v seq="$var" '$1 == seq'

鉴于您发布的示例可能会误导您,您只需要根据要使用正则表达式或字符串以及$ 1上的部分匹配还是完全匹配来选择合适的匹配对象即可。

awk -v seq="$var" '$1 == seq'              # full string
awk -v seq="$var" 'index($1,seq)'          # partial string
awk -v seq="$var" '$1 ~ ("^"seq"$")'       # full regexp
awk -v seq="$var" '$1 ~ seq'               # partial regexp

我们先进行第一个完整的字符串匹配,然后在匹配的$ 1处理后退出:

awk -v seq="$var" '$1 == seq{print; f=1; next} f{exit}'

这将使您的完整代码:

while IFS= read -r var
do  
    awk -v seq="$var" '$1 == seq{print; f=1; next} f{exit}'  filepath/database  
done < "$1" > pattern_matched.file

但是我怀疑您是否根本需要一个shell循环,而您可以这样做:

awk 'NR==FNR{seqs[$1]; next} $1 in seqs' "$1" filepath/database > pattern_matched.file

或其他只有awk(或可能只是join)的变体读取输入文件一次。您可以通过以下方式处理所有seqs[]后进入上述出口:

awk '
    NR==FNR { seqs[$1]; numSeqs++; next }
    $1 in seqs { print; if ($1 !== prev) numSeqs--; prev = $1; next }
    numSeqs == -1 { exit }
' "$1" filepath/database > pattern_matched.file

或类似的

答案 2 :(得分:0)

我认为这应该可以解决问题:

awk -v seq="$var" '{if (match($1, seq)) {print $0; found=1} else if (found) { exit }}'

类似于David C. Rankin的答案,但是不需要将rd=0参数传递给awk,因为在awk中,任何未初始化的变量在首次使用时都会初始化为零。

答案 3 :(得分:0)

由于我们并不十分了解您打算如何使用程序,因此我将为您提供awk解决方案:

awk -v seq="$var" '($1!=seq) { if(p) exit; next }($1==seq){p=1}p'

这使用标志p来检查它是否已经符合序列seq。一个简单的if条件确定是退出awk还是移至下一条记录。找到seq后完成退出,之前完成移至下一条记录。

但是,由于将其置于循环中,因此将一遍又一遍地读取文件。如果要进行子选择,则可以使用James Brown

的解决方案