首页 > 解决方案 > 让 awk 高效(再次)

问题描述

我有下面的代码,它可以成功运行(感谢@EdMorton),用于解析、清理日志文件(非常大)并输出到更小的文件中。输出文件名是每行的前 2 个字符。但是,如果这两个字符中有特殊字符,则需要将其替换为“_”。这将有助于确保文件名中没有非法字符。

接下来,它检查是否有任何输出文件大于特定大小,如果是,则该文件被第三个字符子分割。

处理 1 GB 的日志(在我的笔记本电脑上)大约需要 10 分钟。这可以更快吗?任何帮助将不胜感激。

示例日志文件

"email1@foo.com:datahere2     
email2@foo.com:datahere2
email3@foo.com datahere2
email5@foo.com;dtat'ah'ere2 
wrongemailfoo.com
nonascii@row.com;data.is.junk-Œœ
email3@foo.com:datahere2

预期产出

# cat em 
email1@foo.com:datahere2     
email2@foo.com:datahere2
email3@foo.com:datahere2
email5@foo.com:dtat'ah'ere2 
email3@foo.com:datahere2

# cat _leftover
wrongemailfoo.com
nonascii@row.com;data.is.junk-Œœ

代码:

#/usr/bin/env bash
Func_Clean(){
pushd $1 > /dev/null
    awk '
        {
            gsub(/^[ \t"'\'']+|[ \t"'\'']+$/, "")
            sub(/[,|;: \t]+/, ":")
            if (/^[[:alnum:]_.+-]+@[[:alnum:]_.-]+\.[[:alnum:]]+:/ && /^[\x00-\x7F]*$/) {
                print
            }
            else {
                print >> "_leftover"
            }
        } 
    ' * |
    sort -t':' -k1,1 |
    awk '
        { curr = tolower(substr($0,1,2)) }
        curr != prev {
            close(Fpath)
            Fpath = gensub(/[^[:alnum:]]/,"_","g",curr)
            prev = curr
        }
        { 
            print >> Fpath
            # print | "gzip -9 -f >> " Fpath  # Throws an error
        } ' && rm *.txt

    find * -type f -prune -size +1000000c \( ! -iname "_leftover" \) |while read FILE; do
    awk '
        { curr = tolower(substr($0,1,3)) }
        curr != prev {
            close(Fpath)
            Fpath = gensub(/[^[:alnum:]]/,"_","g",curr)
            prev = curr
        }
        { 
            print >> Fpath
            # print | "gzip -9 -f >> " Fpath   # Throws an error
        } ' "$FILE" && rm "$FILE"
    done

    #gzip -9 -f -r .    # This would work, but is it effecient?
popd > /dev/null
}

### MAIN - Starting Point ###
BASE_FOLDER="_test2"
for dir in $(find $BASE_FOLDER -type d); 
do
    if [ $dir != $BASE_FOLDER ]; then
        echo $dir
        time Func_Clean "$dir"
    fi
done

标签: bashawk

解决方案


Wrt 主题Make awk efficient (again)- awk 非常高效,您正在寻找使特定 awk 脚本更高效的方法,并使调用 awk 的 shell 脚本更高效。

我看到的唯一明显的性能改进是:

  1. 改变:
find * -type f -prune -size +1000000c \( ! -iname "_leftover" \) |
while read FILE; do
    awk 'script' "$FILE" && rm "$FILE"
done

类似于(未经测试):

readarray -d '' files < <(find . -type f -prune -size +1000000c \( ! -iname "_leftover" \) -print0) &&
awk 'script' "${files[@]}" &&
rm -f "${files[@]}"

所以你总共调用一次 awk 而不是每个文件一次。

  1. 对所有目录中的所有文件调用Func_Clean()一次,而不是每个目录调用一次。

  2. 使用GNU 并行或类似方式Func_Clean()在所有目录上并行运行。

我看到您正在考虑将输出管道传输gzip到以节省空间,这很好,但请注意,这会在执行时间方面花费您一些东西(idk 多少)。此外,如果你这样做,那么你需要关闭整个输出管道,因为是你从 awk 写入的内容,而不仅仅是它末尾的文件,所以你的代码将类似于(未经测试):

    { curr = tolower(substr($0,1,3)) }
    curr != prev {
        close(Fpath)
        Fpath = "gzip -9 -f >> " gensub(/[^[:alnum:]]/,"_","g",curr)
        prev = curr
    }
    { print | Fpath }

除了上面的建议之外,这并不是为了加快速度find,它只是清理您问题中的代码以减少冗余和常见错误(UUOC、缺少引号、读取 find 输出的错误方式、不正确使用>>vs >, ETC。)。从这样的事情开始(未经测试,假设您确实需要为每个目录分离输出文件):

#/usr/bin/env bash

clean_in() {
    awk '
        {
            gsub(/^[ \t"'\'']+|[ \t"'\'']+$/, "")
            sub(/[,|;: \t]+/, ":")
            if (/^[[:alnum:]_.+-]+@[[:alnum:]_.-]+\.[[:alnum:]]+:/ && /^[\x00-\x7F]*$/) {
                print
            }
            else {
                print > "_leftover"
            }
        } 
    ' "${@:--}"
}

split_out() {
    local n="$1"
    shift
    awk -v n="$n" '
        { curr = tolower(substr($0,1,n)) }
        curr != prev {
            close(Fpath)
            Fpath = gensub(/[^[:alnum:]]/,"_","g",curr)
            prev = curr
        }
        { print > Fpath }
    ' "${@:--}"
}

Func_Clean() {
    local dir="$1"
    printf '%s\n' "$dir" >&2
    pushd "$dir" > /dev/null
    clean_in *.txt |
        sort -t':' -k1,1 |
            split_out 2 &&
    rm -f *.txt &&
    readarray -d '' big_files < <(find . -type f -prune -size +1000000c \( ! -iname "_leftover" \) -print0) &&
    split_out 3 "${big_files[@]}" &&
    rm -f "${big_files[@]}"
    popd > /dev/null
}

### MAIN - Starting Point ###
base_folder="_test2"
while IFS= read -r dir; do
    Func_Clean "$dir"
done < <(find "$base_folder" -mindepth 1 -type d)

如果我是你,我会从那个开始(在任何必要的测试/调试之后)然后寻找提高性能的方法。


推荐阅读