问个 shell 命令文件行提取问题

2019-03-08 10:49:40 +08:00
 shuizhongyu10
两个文件 A,B,A 单列文件,B 多列文件
对 A 中每一行,到 B 中找对应第一列相同的行,提取出来到 C 文件中。
目前我是这样写的:
cat A | awk '{printf("grep %s B>>C\n",$1)}' | bash
但无奈实在有点慢,这里的 B 文件比较大,300w+行,A 文件 10w+行。
老哥们有什么方案能快点的吗?
3230 次点击
所在节点    Linux
26 条回复
necomancer
2019-03-08 11:38:51 +08:00
你可能需要 look 命令:
man look:
look - display lines beginning with a given string
建议你看看手册。另外,先排序 B 文件会提高很多效率:
sort -d B > Bsorted
while read line
do
look ${line} Bsorted >> C
done < A
希望你的 A,B 都是 unique 的,重复内容浪费资源。
至于 sort 和 uniq,都有比较快的方法,比如 linux 下还能用 parallel 加速。
necomancer
2019-03-08 11:48:52 +08:00
或者使用 sgrep,我记得 sgrep 是需要排序好的文件的,用二分查找。
necomancer
2019-03-08 11:50:22 +08:00
look 也是 binary search,但只能查行头。看实际需求吧。
geelaw
2019-03-08 11:56:29 +08:00
因为你每次都要重新读一次 B 文件 - - 先存到内存里比较好,所以请换 PowerShell 或者脚本语言。

$b = {} # 大小写不敏感,要得到敏感版本,使用 [System.Collections.Generic.Dictionary[string,string]]::new()
gc B | % { $b[$_.Substring(0, $_.IndexOf(' ') - 1)] = $_ }
gc A | % { $b[$_] } | sc C
hugee
2019-03-08 11:59:39 +08:00
@shuizhongyu10 建议把文本发出来,这样我们好测试代码执行速度。
necomancer
2019-03-08 12:05:43 +08:00
每次读文件是个问题,sort -d B > /tmp/Bsorted,如果可以放内存里的话^_^
Sherlocker
2019-03-08 12:26:27 +08:00
给个文本截图啊,如果分隔符都是空格应该一条 awk 命令搞定
awk 'NR==FNR{a[$1]=$0;next}NR>FNR{if($1 in a)print $0}' a b > c
shuizhongyu10
2019-03-08 13:05:27 +08:00
@necomancer 感谢老哥 ,试了下你这种,速度差别不是很大 。最大瓶颈应该就是重复读入了 B 文件,我就是不知道怎么只读入一次
shuizhongyu10
2019-03-08 13:06:45 +08:00
@geelaw 老哥。你这是个什么语言,我都不会用
shuizhongyu10
2019-03-08 13:08:17 +08:00
牛逼了老哥,你这个速度贼快。你这种应该就是解决了多次读入 B 文件的问题,nb !
shuizhongyu10
2019-03-08 13:13:18 +08:00
@Sherlocker 牛逼了老哥,你这个完美解决了多次读 B 的问题,A,B 文件都只读一次,速度贼快。
shuizhongyu10
2019-03-08 13:13:49 +08:00
@hugee 那我一会传网盘,放个链接?
zbinlin
2019-03-08 13:42:33 +08:00
awk 'BEGIN { while ( (getline line<"A") > 0 ) { hash[line] = 1 } } hash[$1] == 1 { print $0 }' B
mononite
2019-03-08 13:56:06 +08:00
A 和 B 都读取一次,先读 A 保存起来,再读 B 过滤,用 awk 处理很直接。
awk 'NR==FNR{a[$1];next}$1 in a' A B
geelaw
2019-03-08 14:28:26 +08:00
@geelaw #4 这里应该改成 $b=@{} 然后 gc 需要调优,正确的代码

measure-command {
$sz = gc b -read 16000 | measure | select -expand count
$sz *= 2 * 16000
$b = [System.Collections.Generic.Dictionary[string,string]]::new($sz)
gc b -read 16000 | % { foreach ($x in $_) {
$b[$x.substring(0,$x.indexof(' '))] = $x
} }
gc a -read 16000 | % { foreach ($x in $_) { $b[$_] } } | sc C -encoding utf8
}

需要 2 min 左右。

不过更好的方法是把 a 存进内存,然后 stream 处理 b,如下

measure-command {
$s = [System.Collections.Generic.HashSet[string]]::new([string[]](gc a))
gc b -read 16000 | % { foreach ($x in $_) { if ($s.contains($x.substring(0,$x.indexof(' ')))) { $x } } } | sc C -encoding utf8
}

只要 12.5s 。

@shuizhongyu10 #9 答案在 #4 第一行
hanxiV2EX
2019-03-08 14:34:29 +08:00
我只会 sort 之后,再 diff。awk 还是不够熟练。
rrfeng
2019-03-08 14:34:32 +08:00
不是 join 吗?
shuizhongyu10
2019-03-08 14:40:32 +08:00
@geelaw 感谢感谢 PowerShell 有机会的话去学一下,目前采用的 awk 方法完全满足了需求。
测了下时间,只要 2.3s ,哈哈。
necomancer
2019-03-08 16:48:11 +08:00
@shuizhongyu10 你是 Linux 系统吗?我这边下文件不是很方便,Linux 下百度网盘神马的太不友好了 T_T,你有没有试试把 A Bsorted 文件都放在 /tmp 下? 一般发行版默认 /tmp 挂载一半内存大小。我也想知道一下效率~
coolloves
2019-03-08 16:57:48 +08:00
@necomancer 试试 baidupcs_go,可以跑满速,我刚下完,渣移动,100M 带宽,10MB 左右速度,差不多跑满了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/542409

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX