V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
Distributions
Ubuntu
Fedora
CentOS
中文资源站
网易开源镜像站
shuizhongyu10
V2EX  ›  Linux

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

  •  
  •   shuizhongyu10 · Mar 8, 2019 · 3879 views
    This topic created in 2608 days ago, the information mentioned may be changed or developed.
    两个文件 A,B,A 单列文件,B 多列文件
    对 A 中每一行,到 B 中找对应第一列相同的行,提取出来到 C 文件中。
    目前我是这样写的:
    cat A | awk '{printf("grep %s B>>C\n",$1)}' | bash
    但无奈实在有点慢,这里的 B 文件比较大,300w+行,A 文件 10w+行。
    老哥们有什么方案能快点的吗?
    Supplement 1  ·  Mar 8, 2019
    A B 文件我放在网盘上了,大家如果有兴趣可以试试
    链接: https://pan.baidu.com/s/1iWWhjhEEpUtMErDCC3Q3kA 提取码: xxr5
    Supplement 2  ·  Mar 8, 2019
    目前采用 @Sherlocker 老哥的方法,速度很快。
    awk 'NR==FNR{a[$1]=$0;next}NR>FNR{if($1 in a)print $0}' a b > c
    26 replies    2019-04-15 11:23:01 +08:00
    necomancer
        1
    necomancer  
       Mar 8, 2019   ❤️ 1
    你可能需要 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
        2
    necomancer  
       Mar 8, 2019
    或者使用 sgrep,我记得 sgrep 是需要排序好的文件的,用二分查找。
    necomancer
        3
    necomancer  
       Mar 8, 2019
    look 也是 binary search,但只能查行头。看实际需求吧。
    geelaw
        4
    geelaw  
       Mar 8, 2019
    因为你每次都要重新读一次 B 文件 - - 先存到内存里比较好,所以请换 PowerShell 或者脚本语言。

    $b = {} # 大小写不敏感,要得到敏感版本,使用 [System.Collections.Generic.Dictionary[string,string]]::new()
    gc B | % { $b[$_.Substring(0, $_.IndexOf(' ') - 1)] = $_ }
    gc A | % { $b[$_] } | sc C
    hugee
        5
    hugee  
       Mar 8, 2019
    @shuizhongyu10 建议把文本发出来,这样我们好测试代码执行速度。
    necomancer
        6
    necomancer  
       Mar 8, 2019
    每次读文件是个问题,sort -d B > /tmp/Bsorted,如果可以放内存里的话^_^
    Sherlocker
        7
    Sherlocker  
       Mar 8, 2019   ❤️ 2
    给个文本截图啊,如果分隔符都是空格应该一条 awk 命令搞定
    awk 'NR==FNR{a[$1]=$0;next}NR>FNR{if($1 in a)print $0}' a b > c
    shuizhongyu10
        8
    shuizhongyu10  
    OP
       Mar 8, 2019
    @necomancer 感谢老哥 ,试了下你这种,速度差别不是很大 。最大瓶颈应该就是重复读入了 B 文件,我就是不知道怎么只读入一次
    shuizhongyu10
        9
    shuizhongyu10  
    OP
       Mar 8, 2019
    @geelaw 老哥。你这是个什么语言,我都不会用
    shuizhongyu10
        10
    shuizhongyu10  
    OP
       Mar 8, 2019
    牛逼了老哥,你这个速度贼快。你这种应该就是解决了多次读入 B 文件的问题,nb !
    shuizhongyu10
        11
    shuizhongyu10  
    OP
       Mar 8, 2019
    @Sherlocker 牛逼了老哥,你这个完美解决了多次读 B 的问题,A,B 文件都只读一次,速度贼快。
    shuizhongyu10
        12
    shuizhongyu10  
    OP
       Mar 8, 2019
    @hugee 那我一会传网盘,放个链接?
    zbinlin
        13
    zbinlin  
       Mar 8, 2019   ❤️ 1
    awk 'BEGIN { while ( (getline line<"A") > 0 ) { hash[line] = 1 } } hash[$1] == 1 { print $0 }' B
    mononite
        14
    mononite  
       Mar 8, 2019
    A 和 B 都读取一次,先读 A 保存起来,再读 B 过滤,用 awk 处理很直接。
    awk 'NR==FNR{a[$1];next}$1 in a' A B
    geelaw
        15
    geelaw  
       Mar 8, 2019   ❤️ 1
    @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
        16
    hanxiV2EX  
       Mar 8, 2019 via Android
    我只会 sort 之后,再 diff。awk 还是不够熟练。
    rrfeng
        17
    rrfeng  
       Mar 8, 2019 via Android
    不是 join 吗?
    shuizhongyu10
        18
    shuizhongyu10  
    OP
       Mar 8, 2019
    @geelaw 感谢感谢 PowerShell 有机会的话去学一下,目前采用的 awk 方法完全满足了需求。
    测了下时间,只要 2.3s ,哈哈。
    necomancer
        19
    necomancer  
       Mar 8, 2019
    @shuizhongyu10 你是 Linux 系统吗?我这边下文件不是很方便,Linux 下百度网盘神马的太不友好了 T_T,你有没有试试把 A Bsorted 文件都放在 /tmp 下? 一般发行版默认 /tmp 挂载一半内存大小。我也想知道一下效率~
    sparkssssssss
        20
    sparkssssssss  
       Mar 8, 2019   ❤️ 1
    @necomancer 试试 baidupcs_go,可以跑满速,我刚下完,渣移动,100M 带宽,10MB 左右速度,差不多跑满了
    shuizhongyu10
        21
    shuizhongyu10  
    OP
       Mar 8, 2019
    @necomancer

    while read line
    do
    look ${line} Bsorted >> C
    done < A

    我用上面你提供的这个方法,测了这样输出 100 条所用的时间,不知道你要的是不是这个。
    放在硬盘和内存里都是 29s 左右。
    我系统是 ubuntu,/tmp 并没有挂载在内存上,我通过 mount -t tmpfs -o size=300m tmpfs dir 挂了一块内存来测的。
    按道理来说放在内存里应该有提升,不知道是测试的量太小还是哪里出了问题?
    hugee
        22
    hugee  
       Mar 8, 2019
    @shuizhongyu10 可以的。然后你说下你的执行时间大概是多少,如果我弄出来效率有提高就告知你。
    zclHIT
        23
    zclHIT  
       Mar 8, 2019 via iPhone
    zclHIT
        24
    zclHIT  
       Mar 8, 2019 via iPhone
    上面的链接应该就是你要做的吧?
    necomancer
        25
    necomancer  
       Mar 8, 2019
    @shuizhongyu10 我没处理过那么大量的 A,频繁打开文件确实是瓶颈,无论文件是否在内存里。刚才搜到一个帖子,看到使用 wile read line 本身就是一个很低效的办法,看来 shell 的确不适合直接做这种事...

    https://unix.stackexchange.com/questions/169716/why-is-using-a-shell-loop-to-process-text-considered-bad-practice

    ...😔
    zclHIT
        26
    zclHIT  
       Apr 15, 2019
    过去了一个月,问个新的问题,反过来要查找文件 a 中所有行,在文件 b 中没有的打印出来到文件 c 应该怎么写呢?
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   3797 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 62ms · UTC 04:40 · PVG 12:40 · LAX 21:40 · JFK 00:40
    ♥ Do have faith in what you're doing.