将文件拆分为多个文件,一次一行

Split file into multiple files one row at a time

我有一个日志文件(大约 50K 行),格式如下:

email1@gmail.com:address0:some_details0
email2@gmail.com:address1:some_details1
email1@yahoo.com:address2:some_details2
email2@yahoo.com:address3:some_details3

我正在尝试读取此文件并将其分成两个文件夹(gmail.com 和 yahoo.com),然后将每一行写入一个以电子邮件 ID 命名的唯一文件。我下面的代码有效,但速度很慢。有人可以帮我让它更快更有效吗?将不胜感激。

#/bin/sh
grep -hv -P "[^[:ascii:]]" * |
awk -F":" '
    {
        if ( ~ /^[[:alnum:]_.+-]+@[[:alnum:]_.-]+\.[[:alnum:]]/ && NF>1 && $NF!="")
        {
            split(, arr, "@")
            system("mkdir -p "tolower(arr[2]))
            print [=11=] >> tolower(arr[2])"/"tolower(arr[1])
        }
    }'

PS:正则表达式是确保电子邮件地址有效的基本检查。我没有做过于繁重的检查。起初我以为正则表达式使我的代码变慢了,但事实并非如此。即使没有正则表达式,代码也非常慢。我认为 I/O 正在使它变慢。我们如何改进?

像这样:

awk -F'[@:]' '{system("mkdir -p 7""7");f="/";print>>f;close(f)}' file

-F'[@:]' 将输入字段定界符设置为 @:,从而使用以下记录:

email1 gmail.com address0 some_details0
email2 gmail.com address1 some_details1
email1 yahoo.com address2 some_details2
email2 yahoo.com address3 some_details3

输出文件名只是第二个字段 + '/' + 当记录像这样拆分时的第一个字段。 print >> "/" 会将当前记录附加到该文件。如果它不存在,awk 将创建它。

close(f) 使用它来确保当输入文件包含(太多)许多不同的域并因此输出文件时我们不会 运行 文件描述符。

它主要是生成一个新的子 shell 来为每个输入行调用一次 mkdir,这使得您的代码 运行 如此缓慢。改为做这样的事情:

filename = tolower(arr[1])
dirname = tolower(arr[2])
if ( !seen[dirname]++ ) {
    system("mkdir -p 7" dirname "7")
}
print > (dirname "/" filename)

所以你只生成一个子 shell 来为每个目录调用一次 mkdir。

请注意,除非您使用的是 GNU awk,否则当您创建了大约十几个输出文件时,您会遇到“打开的文件过多”错误,即使使用 GNU awk,输出文件越多,速度也会越慢您已经打开,因此这也可能会影响您的代码性能。常见的解决方案是先按电子邮件地址对输入文件进行排序,然后在每次电子邮件地址(新输出文件名)更改时关闭当前输出文件。

鉴于此,我真正编写您的程序的方式如下:

#!/usr/bin/env bash

grep -hv -P '[^[:ascii:]]' "${@:--}" |
sort -t':' -k1,1 -s |
awk -F':' '
    !( ~ /^[[:alnum:]_.+-]+@[[:alnum:]_.-]+\.[[:alnum:]]/ && NF>1 && $NF!="") { next }
    { curr = tolower() }
    curr != prev {
        close(out)
        split(curr, arr, /@/)
        filename = arr[1]
        dirname = arr[2]
        if ( !seen[dirname]++ ) {
            system("mkdir -p 7" dirname "7")
        }
        out = dirname "/" filename
        prev = 
    }
    { print > out }
'

我在上面的 -s 中使用 GNU 排序来实现“稳定排序”,如果您没有它并且关心在输出中保留的给定电子邮件地址的输入行的相对顺序,还有其他处理方法,例如awk -v OFS=':' '{print NR, [=13=]}' | sort -t':' -k2,2 -k1,1n | cut -d':' -f2-.