V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
vus520
V2EX  ›  问与答

Linux 下遍历大文件每一行的最佳方法,最好是命令

  •  
  •   vus520 · 2015-12-05 10:46:00 +08:00 · 5453 次点击
    这是一个创建于 3312 天前的主题,其中的信息可能已经有所发展或是发生改变。
    via: http://liuzhiqiangruc.iteye.com/blog/1454797

    原始需求是想监控文件增加的每一行,传给外部程序处理。

    实际上的办法是定时遍历文件的每行,再传给外部程序处理。
    94 条回复    2015-12-06 21:49:48 +08:00
    angelface
        1
    angelface  
       2015-12-05 10:49:21 +08:00
    iNotify
    angelface
        2
    angelface  
       2015-12-05 10:50:06 +08:00
    RemRain
        3
    RemRain  
       2015-12-05 10:53:22 +08:00 via iPhone
    tail -f
    程序实现的话, 2.6.32 内核以上用 inotify ,也可以直接用 libev
    clino
        4
    clino  
       2015-12-05 11:00:42 +08:00
    tail -f +1
    xufang
        5
    xufang  
       2015-12-05 11:09:15 +08:00
    awk 比 perl 要快的,楼主你没用对。
    不信的话,楼主可以把你这个发到 ChinaUnix 的 Shell 板块,一堆人会给出更快的方案。

    不过我现在喜欢用 golang 来做这样的事情,因为楼主你注意到没有,这些脚本写得再高效,也只能单核运行,很难利用多核上。

    而 golang 的话,天然支持多核,用起来很方便。
    http://rodaine.com/2015/04/async-split-io-reader-in-golang

    用这种模式处理大文件话,立马层次就不一样了。
    xufang
        6
    xufang  
       2015-12-05 11:09:50 +08:00
    楼上给出 tail 的,明显是看帖不回帖的,楼主不用理他们。
    vus520
        7
    vus520  
    OP
       2015-12-05 11:12:26 +08:00
    @xufang go lang 大法好
    vus520
        8
    vus520  
    OP
       2015-12-05 11:14:25 +08:00
    @clino tail 可以从头开始遍历一个文件吗,没有找到参数
    xufang
        9
    xufang  
       2015-12-05 11:19:30 +08:00
    而且 golang 的正则引擎虽然残废不完备,但是比较快。秒杀了很多脚本语言。
    具体可以见 russ cox (golang 主要贡献者)的论文
    《 Regular Expression Matching Can Be Simple And Fast 》
    https://swtch.com/~rsc/regexp/regexp1.html

    这标题,啧啧,狠狠的打脸啊。
    skydiver
        10
    skydiver  
       2015-12-05 11:22:34 +08:00
    @vus520 可以。 tail -n +N 就是从头第 N 行开始,想从头开始直接 tail -f -n +1 就可以了
    skydiver
        11
    skydiver  
       2015-12-05 11:23:14 +08:00
    @xufang
    skydiver
        12
    skydiver  
       2015-12-05 11:25:10 +08:00
    @xufang 吹 Go 没关系,用不着靠贬低 tail 来说事儿,不觉得拿 Go 和 tail 比太 low 了么。
    另外不要养成没事儿扣帽子的习惯。
    xufang
        13
    xufang  
       2015-12-05 11:26:34 +08:00
    @skydiver tail 的楼主的原始需求,亲。楼主后来的需求是遍历大文件每一行哦。
    skydiver
        14
    skydiver  
       2015-12-05 11:28:08 +08:00
    @xufang 另外 lz 题目里写了最好是命令,回复 tail 怎么就成了回帖不看贴了
    哦,你说的看贴不回贴。我们这不是回帖了么,怎么叫不回贴了
    skydiver
        15
    skydiver  
       2015-12-05 11:29:08 +08:00
    @xufang 这个需求 tail 就可以满足,而且还满足 lz 说的最好是命令
    xufang
        16
    xufang  
       2015-12-05 11:29:09 +08:00
    @xufang 打快了就发出了。

    原始需求用 tail -f 没错,新需求和 tail 完全不搭嘎。
    awk/perl 适合,我鼓吹一下 golang 也挺适合。并不是那它和 tail 比啊。
    xufang
        17
    xufang  
       2015-12-05 11:30:57 +08:00
    @skydiver 这,如果是 tail -f -n +1 完全是赶鸭子上架,后面正则匹配和替换等等,完全不搞不定。
    skydiver
        18
    skydiver  
       2015-12-05 11:31:41 +08:00
    @xufang 你需要看看 lz 的原始需求。
    并没有什么新需求, lz 说了那只是实际上的办法。
    有好的办法解决原始需求,何必纠结这个实际方法。
    skydiver
        19
    skydiver  
       2015-12-05 11:32:47 +08:00
    @xufang 通过管道传给 awk sed perl 或者自己写的程序处理就可以了,这就是*nix 哲学
    xufang
        20
    xufang  
       2015-12-05 11:37:33 +08:00
    @skydiver 你看下楼主的帖子。那三个脚本
    xufang
        21
    xufang  
       2015-12-05 11:39:05 +08:00
    @skydiver 呃,你如果转进到 Unix 哲学,我只能推测你估计没有像我和楼主一样真实处理过大文件的文本。。。
    Andiry
        22
    Andiry  
       2015-12-05 11:48:38 +08:00
    500w+行,每行算 100bytes ,也就是 500MB ,也不算什么大文件,全部读进内存,然后开多线程用 seek 分段处理就完了
    skydiver
        23
    skydiver  
       2015-12-05 11:49:01 +08:00
    @xufang 这只是你的不合理推测。
    逐行处理不需要把文件全读到内存里,所以跟文件大小没有关系。
    vus520
        24
    vus520  
    OP
       2015-12-05 11:49:16 +08:00
    @xufang @skydiver 二位这么执着我真是又醉又感动

    现在的需求其实是找出每一行数据,交给其它进程数据,现在只是想知道,哪种方式最快最方便最省内存

    我依次测试过 cat 再 loop ,试过 awk ,现在的场景是处理 50G 以上的日志文件分析进行抽取。

    考虑到生产环境中并没有安装 go ,所以命令是优先的,方便集群中部署。

    我正在测试几个命令的效率,回头跟大家 update 下
    vus520
        25
    vus520  
    OP
       2015-12-05 11:51:04 +08:00
    @skydiver 是的,我的需求跟你说的是一致的。不管文件多大,我每次只处理一行,直接扔给外部程序处理,这里只关注遍历文件的速度,外部程序处理的速度暂时不关注
    xufang
        26
    xufang  
       2015-12-05 11:54:06 +08:00 via Android
    @Andiry golang 欢迎您。[微笑]
    xufang
        27
    xufang  
       2015-12-05 11:58:35 +08:00 via Android
    @skydiver 。。。 这,好吧,您继续坚持 tail 遍历文件吧。,我是说服不了啦。
    xufang
        28
    xufang  
       2015-12-05 12:07:42 +08:00
    @Andiry 补上 https://blog.klauspost.com/an-async-read-ahead-package-for-go/

    这套东东比手动撸 c++/java 舒服多了。至于脚本语言,有了 GIL 就别想啦。
    clino
        29
    clino  
       2015-12-05 12:16:54 +08:00
    @vus520 tail 可以从头开始遍历啊 -n 够大就可以从头开始
    xufang
        30
    xufang  
       2015-12-05 12:18:25 +08:00
    @clino 亲,也请再看下楼主原帖哦,楼主要的是一个高效的遍历文件每一行并且做正则替换的方法。。。
    ryd994
        31
    ryd994  
       2015-12-05 12:26:17 +08:00
    问题是为什么不用 tail -f 解决原始需求而是用这种绕路的方法解决一个人为的需求?

    对于新需求, BC 和 EF 之间是否有顺序关系, BC 是否一定在 EF 之前, EF 是否只出现一次?如果是: sed

    还有,如果你有多核的话,可以直接 seek 到 1/n 大小处,找到最近的换行,然后就可以并行化了。注意处理好边界问题。

    你现在的方法估计还有个瓶颈在 IO 上,因为修改的部分不大,但要复制一份的 IO 不小。直接写个 C 程序处理并不复杂,直接在当前位置写入,表面上看是随机写,但有缓存的情况下不需要担心。
    xufang
        32
    xufang  
       2015-12-05 12:28:53 +08:00 via Android
    @ryd994 楼主这需求其实是离线收集日志,估计还用上了 fabric ansible 这些工具。所以才会这样,你琢磨一下。
    ryd994
        33
    ryd994  
       2015-12-05 12:32:06 +08:00
    @xufang 那写个 C 的小程序是值得的, 反正只用到基本的文件读写方面的知识,写个真不难
    xufang
        34
    xufang  
       2015-12-05 12:33:39 +08:00 via Android
    嗯,不过我现在成了 go 粉了,在不遗余力地游说楼主呢。
    ryd994
        35
    ryd994  
       2015-12-05 12:33:48 +08:00
    这个任务,瓶颈是 IO ,有条件的话上 SSD 或者全部放内存
    ryd994
        36
    ryd994  
       2015-12-05 12:35:41 +08:00
    @xufang 文件读写这件事由硬件制约,并不是太适合高度并行化,用 Go 未必合适。
    clino
        37
    clino  
       2015-12-05 13:15:20 +08:00
    @xufang 这有什么问题吗? 如果要做正则替换就做啊.
    xufang
        38
    xufang  
       2015-12-05 13:16:22 +08:00
    @ryd994 楼主还要正则替换呢
    具体请看我 28 楼回复的文章,很详细了。
    xufang
        39
    xufang  
       2015-12-05 13:17:31 +08:00
    @clino 楼主要的高效哦,在他的原帖拒绝了管道的方案。
    clino
        40
    clino  
       2015-12-05 13:27:59 +08:00
    @xufang 要的是高效 管道不一定会低效 原帖低效的原因是每一行都重新调用一个新进程,如果用同样一个进程效率不会这么低
    xufang
        41
    xufang  
       2015-12-05 13:29:13 +08:00
    @clino 请看完原帖啊,所以楼主那他的 perl/awk 的改进版来挑战呀,我用 golang 排了一下。
    clino
        42
    clino  
       2015-12-05 13:48:54 +08:00
    @xufang 没看到你的结果啊 要楼主实际比较过才行吧
    另外我看了一下上面楼主根本不想用 go 啊...
    xufang
        43
    xufang  
       2015-12-05 13:51:29 +08:00
    @clino https://github.com/klauspost/readahead 饭这个不能让人喂的啊。

    呵呵,单核性能够用就行,但是纯讨论了话,当然是精益求精了。
    xufang
        44
    xufang  
       2015-12-05 13:52:32 +08:00
    @clino 另外,不要做伸手党哦。
    clino
        45
    clino  
       2015-12-05 13:59:09 +08:00
    @xufang 有点莫名其妙 什么伸手党
    我没有要吃饭 你硬塞给我个 go 的玩意说是喂饭 然后说我伸手党 是有什么问题? 难道是 go 教里的原教旨主义?

    我是说楼主一样的原始文件那两种方法都有测试时间结果 那要有 go 的方法对比测试一下时间才是实际
    xufang
        46
    xufang  
       2015-12-05 14:01:52 +08:00
    @clino 呵呵,转进的真好。
    xufang
        47
    xufang  
       2015-12-05 14:04:14 +08:00
    诸位觉得我的理论无懈可击了,所以逼我 show 代码了吗?

    老实说,这块代码我也是花了一些心思写的,觉得是业内前沿的技术。不愿意白给的哦。

    反正思路我也指出了,看客们自己判断。
    kotokz
        48
    kotokz  
       2015-12-05 14:17:19 +08:00
    这种我肯定会用 nim +mmap
    pright
        49
    pright  
       2015-12-05 15:58:55 +08:00
    #一粉顶十黑的绝佳例子#
    xufang
        50
    xufang  
       2015-12-05 16:03:26 +08:00 via iPhone
    @pright 辩不过就加 tag ,这样的 V2EX 人才越来越兴旺了。
    xufang
        51
    xufang  
       2015-12-05 16:07:48 +08:00
    在重复一下我在 /t/240708 的观点,以目前小学生泛滥的情况,我真的不愿意下场撕了。

    夏虫不可语冰,年纪轻轻就学会了拒绝新事物新技术的,真是够了。
    5thcat
        52
    5thcat  
       2015-12-05 16:53:27 +08:00
    顺序读文件还要并行真是醉了, 真是给 golang 抹黑
    undeflife
        53
    undeflife  
       2015-12-05 16:55:20 +08:00   ❤️ 1
    楼上这位还真是 手里拿着锤子,看什么都像是钉子 ...

    golang 没什么,golang 教徒就很让人烦了
    xufang
        54
    xufang  
       2015-12-05 16:55:46 +08:00
    @5thcat 切,正则处理不耗 CPU ?这位新来的,有一个回帖不看贴的。
    xufang
        55
    xufang  
       2015-12-05 16:56:18 +08:00
    @undeflife 嗯嗯,帽子发得很溜,在我看来你比你楼上还要 LOW
    5thcat
        56
    5thcat  
       2015-12-05 16:57:09 +08:00
    @xufang 就问一句:多核能加速顺序文件读吗?
    xufang
        57
    xufang  
       2015-12-05 16:57:21 +08:00
    来来来,让我看下 V2EX 今天又多少小学生在线。 :)
    xufang
        58
    xufang  
       2015-12-05 16:58:16 +08:00
    @5thcat 呵呵,不用并行,你能在等 IO 的时候,把 CPU 也用上吗?
    xufang
        59
    xufang  
       2015-12-05 16:59:41 +08:00
    诸位小学生,回帖先看贴,我在前讲过 N 次我的观点了,不要略过。
    xufang
        60
    xufang  
       2015-12-05 17:22:12 +08:00
    舌战了一下午,除了这幅图,没有其他可以表达我此刻的感受了。

    looyao
        61
    looyao  
       2015-12-05 18:25:15 +08:00
    这里可以粘帖代码么,额
    //tail_demo.c

    #include <stdio.h>
    #include <strings.h>
    #include <stdlib.h>
    #include <unistd.h>

    #include <sys/inotify.h>


    int tail(char *path, long *pos);

    int main(int argc, char *argv[])
    {
    int inotify_fd, watch_fd, n;
    long pos;
    struct inotify_event event;
    void *evp;
    char *path;

    if (argc != 2) {
    return -1;
    }

    path = argv[1];
    pos = 0;

    if (tail(path, &pos) < 0) {
    return -2;
    }

    evp = &event;

    inotify_fd = inotify_init();
    watch_fd = inotify_add_watch(inotify_fd, path, IN_MODIFY | IN_DELETE_SELF | IN_MOVE_SELF);
    if (watch_fd <= 0) {
    fprintf(stderr, "inotify_add_watch error:%s\n", path);
    return -3;
    }

    //这里可以使用 select, poll 轮询下 inotify_fd

    while (1) {
    bzero(&event, sizeof(struct inotify_event));
    n = read(inotify_fd, evp, sizeof(struct inotify_event));
    if (n <= 0) {
    fprintf(stderr, "read error\n");
    break;
    }

    if (event.mask & IN_MODIFY) {
    tail(path, &pos);
    }
    }

    }

    int tail(char *path, long *pos)
    {
    FILE *handle;
    char buf[1024];

    handle = fopen(path, "r");
    if (!handle) {
    fprintf(stderr, "can't open file:%s\n", path);
    return -1;
    }

    fseek(handle, *pos, SEEK_SET);

    while (!feof(handle)) {
    bzero(buf, sizeof(buf));
    fgets(buf, sizeof(buf), handle);
    printf("%s", buf);
    *pos = ftell(handle);
    }

    fclose(handle);
    }


    编译:
    gcc tail_demo.c -o tail_demo
    使用:
    ./tail_demo 文件路径
    xufang
        62
    xufang  
       2015-12-05 18:39:20 +08:00
    牛哄哄喷我的小学生呢,不要停啊。继续喷,来拓展我的世界观。
    vus520
        63
    vus520  
    OP
       2015-12-05 18:52:16 +08:00
    @looyao

    我编译了一下, mac 上找不到 sys/inotify.h

    不过却找到了这个东西
    https://github.com/facebook/watchman
    looyao
        64
    looyao  
       2015-12-05 18:58:15 +08:00
    @vus520 不好意思, Linux 才可以,没有说明。代码临时写的,可能不够严谨,其实就是这个意思,默认读取文件,全部读取完成后监控文件改变再读,这样。不用怕大文件,其实没什么关系。 PS :代码粘上来米有缩进,好丑,下次不粘了,哈哈。
    neoblackcap
        65
    neoblackcap  
       2015-12-05 18:59:17 +08:00
    @vus520 inotify 是 linux 内核特性, Mac 自然没有。
    aku
        66
    aku  
       2015-12-05 19:33:07 +08:00 via Android
    @xufang 不愿意 show code 的话, release binary 就可以了

    go 的好处这时候就体现出来了
    clino
        67
    clino  
       2015-12-05 19:37:34 +08:00
    @neoblackcap 为什么 tail 不需要用到 inotify 就可以监控文件改变呢
    xufang
        68
    xufang  
       2015-12-05 19:38:00 +08:00
    @aku 偏不,我也生气了,就一起口炮下去。
    xufang
        69
    xufang  
       2015-12-05 19:39:50 +08:00
    @clino 简单的很, 在文件尾 while(1) {read(n); sleep(m)} 就可以了, 你试下就知道
    xufang
        70
    xufang  
       2015-12-05 19:41:59 +08:00
    @clino 一个小技巧就是 read(n) 每每读不到数据的时候,以斐波那契数列的时间来 sleep ,当然这个技巧有无效果就见仁见智了。 10 年前,国外的邮件列表就对这个讨论得很充分了。
    skydiver
        71
    skydiver  
       2015-12-05 19:44:45 +08:00
    @clino tail 用的就是 inotify
    xufang
        72
    xufang  
       2015-12-05 19:49:29 +08:00
    skydiver
        73
    skydiver  
       2015-12-05 19:53:49 +08:00
    @xufang 我说的 gnu 的 coreutils ,你拿个 busybox 出来做甚
    xufang
        74
    xufang  
       2015-12-05 19:56:53 +08:00
    @skydiver 呵呵,转进的好。
    skydiver
        75
    skydiver  
       2015-12-05 20:02:51 +08:00
    @xufang 呵呵。 block 不送。
    xufang
        76
    xufang  
       2015-12-05 20:04:25 +08:00
    终于有人使出 block 大法了,我还以为小学生们会比较好面子的死撑的呢。
    clino
        77
    clino  
       2015-12-05 20:28:42 +08:00
    @skydiver 我搜了一下应该两种方法都有支持,优先 inotify 不行就用 polling 的方式
    skydiver
        78
    skydiver  
       2015-12-05 20:31:20 +08:00
    @clino 是的 我记得用不了 inotify 的时候会有提示 使用 polling
    xufang
        79
    xufang  
       2015-12-05 20:34:40 +08:00 via Android
    一个简单的事实: tail 的历史比 inotify 要老,所以小学生哪来的如此的自信满满?
    wjchen
        80
    wjchen  
       2015-12-05 20:52:19 +08:00
    可以 hook 写文件的 api 。
    msg7086
        81
    msg7086  
       2015-12-06 07:33:36 +08:00
    好好的 V2EX 搞得乌烟瘴气,能不能消停下?
    xufang
        82
    xufang  
       2015-12-06 08:50:14 +08:00 via Android
    @msg7086 一开始我回帖是很平和的哦

    结果小学生学艺不精不自量力喷我反被喷

    怪我咯?
    RemRain
        83
    RemRain  
       2015-12-06 12:22:57 +08:00
    @xufang
    LZ 的需求是: 1. 监控文件增加的每一行; 2. 简单的字符串替换; 3. 最好是命令行

    需求里面监控文件增加才是难点,字符串替换怎么来都不是问题,所以楼下的建议都是 tail -f 作监控,管道来处理字符串替换,你这假装很有经验,不给出有帮助的回答并强行与所有人撕逼是为哪般?


    另外你答的几个看起来很厉害,实际没任何用的技术点,我可以给你分析下:

    1. awk 比 Perl 快, LZ 没用对, ChinaUnix 上很多人可以给出更快的方案

    事实上在文本简单处理上,各语言在效率上没有太大差异,处理速度完全取决于 IO 操作。 Linux 是有文件缓存的, LZ 之所以发现 Perl 比 awk 快,很有可能是 awk 后文件被缓存了一部分,之后 Perl IO 操作变快了。不看脚本内容,直接断言 LZ 没用对,而且不说哪里不对,说明你压根没意识到文件缓存的问题,或者说不知道有这个东西。

    2. 脚本写得再高效,只能单核运行,应该用 golang , golang 天然支持多核

    我以为 LZ 需求的效率取决于 IO 性能是有目共睹的,多核处理并不能加快 IO 处理速度,@5thcat 已经说明了。你回复说`你能在等 IO 的时候,把 CPU 也用上吗?` 我反问你一句,你把 CPU 都用上了,就不用等 IO 吗?

    3. golang 的正则引擎秒杀很多脚本语言,打了很多语言的脸

    那又如何? LZ 关心的是遍历,标题说明了,回复中也强调了。正则引擎快有啥用?另外真计较性能的话,你不觉得应该用普通的字符串查找、替换函数吗?锤子是好使,可这是螺丝啊
    xufang
        84
    xufang  
       2015-12-06 12:25:41 +08:00
    @RemRain 有一个看贴不回帖了,我的反复讲过,并行的好处就可把正则运算这部分工作摊到等 IO 时间做。

    懂了吗?
    xufang
        85
    xufang  
       2015-12-06 12:26:36 +08:00
    我敢打赌,你们这些乱喷的小学生根本没看我贴的两篇的文章。
    RemRain
        86
    RemRain  
       2015-12-06 12:32:48 +08:00
    @xufang 什么叫看贴不回帖?为何要用正则运算?你承认不知道文件缓存了?
    xufang
        87
    xufang  
       2015-12-06 12:34:29 +08:00
    @RemRain 为什么要用正则运算?自己去看楼主的原贴。

    在我看来你才不知道呢,你千万别又 low 到这种程度。
    xufang
        88
    xufang  
       2015-12-06 12:35:59 +08:00
    只会爬楼,不看各楼引用的帖子,真是够了。我下午还要出去玩,有什么干货赶紧说。
    RemRain
        89
    RemRain  
       2015-12-06 12:43:15 +08:00
    @xufang 可字符串查找、替换之类的函数更简单更快啊。另外,还是请教下,什么叫看贴不回帖?
    xufang
        90
    xufang  
       2015-12-06 12:45:23 +08:00
    @RemRain 所以你这个说法我们反对啊。不过即使查找替换也是 CPU 操作不是。

    但是一般业务用正则更普遍,所以我就安利了 golang 的正则引擎。

    刚才打快了,是回帖不看贴哦。
    xufang
        91
    xufang  
       2015-12-06 12:45:49 +08:00
    s/我们反对 /我没反对 /
    xufang
        92
    xufang  
       2015-12-06 13:02:23 +08:00
    出门了,最后我甩个半干货出来。

    并行处理存在理论上的优势,但是实现的时候要考虑 locality 的问题, golang 还有 gc 方面需要针对性优化。

    总之,知易行难。写代码其实是个技术活。
    xufang
        93
    xufang  
       2015-12-06 21:48:35 +08:00
    周末在这贴帖子我关注了两天,最终还是没有人提到 parallel 命令,这个让我进一步确认了在 V2EX 的技术自信
    https://en.wikipedia.org/wiki/GNU_parallel

    parallel 命令的思想和我说的这个初衷类似,不过有两点毛病
    1. 需要处理的数据行要上下文无关。
    2. 最要命的是 locality 不行,进程间通信比 golang 实现的原子操作慢多了。

    不过,在 CPU 操作很密集的情况下,不愿意自己撸代码的,可以考虑用它。
    xufang
        94
    xufang  
       2015-12-06 21:49:48 +08:00
    最后送本楼乱喷我的小学生们一句话,除了对新技术保持谦虚之外,回帖要看贴。

    以上。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2611 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 33ms · UTC 11:33 · PVG 19:33 · LAX 03:33 · JFK 06:33
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.