Сделайте awk эффективным (снова) - PullRequest
0 голосов
/ 20 июня 2020

У меня есть приведенный ниже код, который успешно работает (спасибо @EdMorton) и используется для синтаксического анализа, очистки файлов журналов (очень большого размера) и вывода в файлы меньшего размера. Выходное имя файла - это первые 2 символа каждой строки. Однако, если в этих двух символах есть специальный символ, его необходимо заменить на '_'. Это поможет убедиться, что в имени файла нет недопустимого символа.

Затем он проверяет, не превышает ли какой-либо из выходных файлов определенный размер, и если да, то этот файл разбивается на части по 3-му символу.

Обработка журналов объемом 1 ГБ (на моем ноутбуке) займет около 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

Ответы [ 2 ]

0 голосов
/ 20 июня 2020

Вы усложняете себе жизнь тяжелее, чем должно быть. Чтобы разделить ваш журнал в файл em с очищенными адресами и поместить остальные в _leftover, вам просто нужно идентифицировать строки, соответствующие /email[0-9]+@/, а затем применить все необходимые меры очистки (например, удалить что-либо до "email[0-9]+@", преобразовать любые включенные ';' в ':', добавить при необходимости ). Затем вы просто перенаправляете очищенные строки на em и переходите к следующей записи.

    /email[0-9]+@/ {
        $0 = substr($0,match($0,/email[0-9]+@/))
        gsub(/;/,":")
        # add any additional sanitizations here
        print > "em"
        next
    } 

Следующее правило просто собирает оставшиеся строки в массиве.

    {a[++n] = $0}

Последнее правило (правило END) просто перебирает массив, перенаправляя содержимое на _leftover.

    END {
        for (i=1; i<=n; i++)
            print a[i] > "_leftover"
    }

Просто объедините свои правила в окончательный сценарий. Например:

awk '
    /email[0-9]+@/ {
        $0 = substr($0,match($0,/email[0-9]+@/))
        gsub(/;/,":")
        # add any additional sanitizations here
        print > "em"
        next
    } 
    {a[++n] = $0}
    END {
        for (i=1; i<=n; i++)
            print a[i] > "_leftover"
    }
' file

При работе с awk - он будет читать каждую строку (запись), а затем применять каждое написанное вами правило по порядку к каждой записи. Таким образом, вы просто пишете и заказываете правила, необходимые для обработки текста в каждой строке.

Вы можете использовать next, чтобы перейти к следующей записи, чтобы помочь контролировать logi c между правилами (вместе со всеми другими условиями, например if, else, ...). Руководство по GNU awk - хороший справочник, который будет всегда под рукой при изучении awk.

Пример использования / вывода

С вводом в file вы получите следующее в em и _leftover:

$ 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-Œœ

Как уже отмечалось, этот скрипт просто обрезает все, что до email...@, и заменяет все ';' на ':' - вы необходимо добавить любые необходимые дополнительные очистки там, где это указано.

0 голосов
/ 20 июня 2020

По теме Make awk efficient (again) - awk чрезвычайно эффективен, вы ищете способы сделать ваши конкретные сценарии awk более эффективными и сделать сценарий оболочки, который вызывает awk, более эффективным.

Единственное очевидное улучшения производительности, которые я вижу:

  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 один раз всего, а не один раз на файл.

Вызов Func_Clean() всего один раз для всех файлов во всех каталогах вместо одного раза для каждого каталога.

Используйте GNU parallel или аналогичный для запуска 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, это просто очистка кода в вашем вопросе для уменьшения избыточности и распространенных ошибок (UUO C, отсутствие кавычек, неправильный способ чтения вывода find, неправильное использование >> vs >, et c.). Начните с чего-то вроде этого (непроверено и предполагается, что вам нужно разделить выходные файлы для каждого каталога):

#/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)

На вашем месте я бы начал с этого (после любого необходимого тестирования / отладки) и ЗАТЕМ поищите способы улучшить производительность.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...