【问题标题】:Using bash to query a large tab delimited file使用 bash 查询大的制表符分隔文件
【发布时间】:2020-01-26 04:14:23
【问题描述】:

我有一个姓名和 ID 列表(50 个条目)

cat input.txt

name    ID
Mike    2000
Mike    20003
Mike    20002

还有一个巨大的压缩文件(13GB)

zcat clients.gz

name    ID  comment
Mike    2000    foo
Mike    20002   bar
Josh    2000    cake
Josh    20002   _

我的预期输出是

NR  name    ID  comment
1    Mike   2000    foo
3    Mike   20002   bar

clients.gz 中的每个 $1"\t"$2 都是唯一标识符。 input.txt 中的某些条目可能在 clients.gz 中丢失。因此,我想将 NR 列添加到我的输出中以找出丢失的内容。我想使用 zgrep。 awk 需要很长时间(因为我必须 zcat 解压缩我假设的压缩文件?)

我知道zgrep 'Mike\t2000' 不起作用。我想我可以用 awk FNR 解决的 NR 问题。

到目前为止,我有:

awk -v q="'" 
'
NR > 1 {
print "zcat clients.gz | zgrep -w $" q$0q
}' input.txt |
bash > subset.txt

【问题讨论】:

  • 一直在考虑这个问题。我将 zgrep 与 awk 混合并运行一个数组。虽然不确定是否有更简单的方法?我不知道该怎么做zgrep '$Mike\t2002'
  • 如果您对此感兴趣,打印出客户文件中不存在的条目不是更有意义吗?
  • @Shawn 评论栏是我感兴趣的,还有一些我简化了。

标签: bash awk grep zip zgrep


【解决方案1】:
$ cat tst.awk
BEGIN { FS=OFS="\t" }
{ key = $1 FS $2 }
NR == FNR { map[key] = (NR>1 ? NR-1 : "NR"); next }
key in map { print map[key], $0 }

$ zcat clients.gz | awk -f tst.awk input.txt -
NR      name    ID      comment
1       Mike    2000    foo
3       Mike    20002   bar

【讨论】:

  • 非常感谢您的帮助。它在测试文件上返回一个空输出。我试图了解哪里出了问题
  • 通常怀疑是 DOS 行尾。尝试在 input.txt 上运行 dos2unix 或类似命令,然后再次运行该命令。有关该问题的更多信息,请参阅stackoverflow.com/q/45772525/1745001。如果不是这样,请确保您在两个文件中的字段确实像您所说的那样是制表符分隔的。
  • 哦,在几乎任何 UNIX 工具中,- 代替文件名意味着“stdin”,所以这就是告诉 awk 在读取输入后读取来自zcat ... | 管道的输入。文本文件。它类似于 Cyrus 脚本中的 <(zcat clients.gz),但不是特定于 bash 的。
  • 耗时 20 分钟和 2.5M 内存。最后一个问题,如何添加列名?
  • 默认情况下应该打印它们(请参阅我的答案中的输出)。如果不是,则每个文件的第一行看起来不像您在问题示例中显示的那样。
【解决方案2】:

使用 GNU awk 和 bash:

awk 'BEGIN{FS=OFS="\t"} 
     # process input.txt
     NR==FNR{
       a[$1,$2]=$1 FS $2
       line[$1,$2]=NR-1
       next
     }
     # process <(zcat clients.gz)
     {
       $4=a[$1,$2]
       if(FNR==1)
         line[$1,$2]="NR"
       if($4!="")
         print line[$1,$2],$1,$2,$3
     }' input.txt <(zcat clients.gz)

输出:

NR 姓名 ID 注释 1 迈克 2000 英尺 3 迈克 20002 酒吧

一行:

awk 'BEGIN{FS=OFS="\t"} NR==FNR{a[$1,$2]=$1 FS $2; line[$1,$2]=NR-1; next} {$4=a[$1,$2]; if(FNR==1) line[$1,$2]="NR"; if($4!="")print line[$1,$2],$1,$2,$3}' input.txt <(zcat clients.gz)

请参阅:Joining two files based on two key columns awk8 Powerful Awk Built-in Variables – FS, OFS, RS, ORS, NR, NF, FILENAME, FNR

【讨论】:

  • 这会比 zgrep 快吗?
  • 我无法回答这个问题。
  • 谢谢! NR-1 看起来很干净。我假设它将小的输入文件写入内存(循环通过第一部分),然后扫描第二个文件(zcat clients.gz)
  • Per this item ... $4=a[$1,$2] 不会为zcat clients.gz 生成的每一行创建一个数组元素吗? if ( ($1 FS $2) in a) 不会需要更少的内存吗?
  • @EdMorton 谢谢,我想确保我没有遗漏 a) 数组引用作为测试(你和我昨天进行的交流)和 b) 分配中的数组引用
【解决方案3】:

[编辑]
我误解了前置行号的来源。已更正。

你会尝试以下方法:

declare -A num          # asscoiates each pattern to the line number
mapfile -t ary < <(tail -n +2 input.txt)
pat=$(IFS='|'; echo "${ary[*]}")
for ((i=0; i<${#ary[@]}; i++)); do num[${ary[i]}]=$((i+1)); done
printf "%s\t%s\t%s\t%s\n" "NR" "name" "ID" "comment"
zgrep -E -w "$pat" clients.gz | while IFS= read -r line; do
    printf "%d\t%s\n" "${num[$(cut -f 1-2 <<<"$line")]}" "$line"
done

输出:

NR  name    ID  comment
1   Mike    2000    foo
3   Mike    20002   bar
  • 第二行和第三行从input.txt 生成Mike 2000|Mike 20003|Mike 20002 的搜索模式。
  • for ((i=0; i&lt;${#ary[@]}; i++)); do .. 行从 模式到数字。
  • 表达式"${num[$(cut -f 1-2 &lt;&lt;&lt;"$line")]}" 检索行 输出的第一个和第二个字段中的数字。

如果性能仍然不理想,请考虑ripgrep,它比grepzgrep快很多。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-31
    • 1970-01-01
    • 1970-01-01
    • 2021-06-15
    • 1970-01-01
    相关资源
    最近更新 更多