Запустить awk параллельно - PullRequest
0 голосов
/ 18 июня 2020

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

Обработка журналов объемом 1 ГБ (на моем ноутбуке) займет около 12–14 минут. Можно ли это сделать быстрее?

Можно ли запустить это параллельно? Я знаю, что могу сделать }' "$FILE" &. Однако я тестировал, и это мало помогает. Можно ли попросить awk выводить параллельно - что эквивалентно print $0 >> Fpath &?

Любая помощь будет принята с благодарностью.

Пример файла журнала

"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 errorfile
wrongemailfoo.com
nonascii@row.com;data.is.junk-Œœ

Код:

#/bin/sh
pushd "_test2" > /dev/null
for FILE in *
do
    awk '
    BEGIN {
        FS=":"
    }
    {
        gsub(/^[ \t"'\'']+|[ \t"'\'']+$/, "")
        $0=gensub("[,|;: \t]+",":",1,$0)
        if (NF>1 && $1 ~ /^[[:alnum:]_.+-]+@[[:alnum:]_.-]+\.[[:alnum:]]+$/ && $0 ~ /^[\x00-\x7F]*$/)
        {
            Fpath=tolower(substr($1,1,2))
            Fpath=gensub("[^[:alnum:]]","_","g",Fpath)
            print $0 >> Fpath
        }
        else
            print $0 >> "errorfile"
    }' "$FILE"
done
popd > /dev/null

Ответы [ 2 ]

1 голос
/ 18 июня 2020

Посмотрите справочную страницу инструмента GNU с именем parallel, если вы хотите запускать вещи параллельно, но мы можем значительно улучшить скорость выполнения, просто улучшив ваш скрипт.

Текущий скрипт допускает 2 ошибки это сильно влияет на эффективность:

  1. Вызов awk один раз для каждого файла, а не один раз для всех файлов, и
  2. Оставление всех выходных файлов открытыми во время работы скрипта, поэтому awk должен управлять ими

В настоящее время вы, по сути, делаете:

for file in *; do
    awk '
        {
            Fpath = substr($1,1,2)
            Fpath = gensub(/[^[:alnum:]]/,"_","g",Fpath)
            print > Fpath
        }
     ' "$file"
done

Если вы сделаете это вместо этого, он будет работать намного быстрее:

sort * |
    awk '
        { curr = substr($0,1,2) }
        curr != prev {
            close(Fpath)
            Fpath = gensub(/[^[:alnum:]]/,"_","g",curr)
            prev = curr
        }
        { print > Fpath }
    '

Сказав это, вы манипулируем вашими входными строками, прежде чем выяснять имена выходных файлов, поэтому - это не проверено, но я ДУМАЮ, что весь ваш скрипт должен выглядеть так:

#/usr/bin/env bash

pushd "_test2" > /dev/null

awk '
    {
        gsub(/^[ \t"'\'']+|[ \t"'\'']+$/, "")
        sub(/[,|;: \t]+/, ":")
        if (/^[[:alnum:]_.+-]+@[[:alnum:]_.-]+\.[[:alnum:]]+:[\x00-\x7F]+$/) {
            print
        }
        else {
            print > "errorfile"
        }
    } 
' * |
sort -t':' -k1,1 |
awk '
    { curr = substr($0,1,2) }
    curr != prev {
        close(Fpath)
        Fpath = gensub(/[^[:alnum:]]/,"_","g",curr)
        prev = curr
    }
    { print > Fpath }
'

popd > /dev/null

Обратите внимание на использование $0 вместо $1 в сценариях - это еще одно улучшение производительности, потому что awk выполняет разделение полей (что, конечно, требует времени), если вы указываете в своем сценарии определенные поля c.

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

Предполагая, что доступно несколько ядер, простой способ параллельной работы - использовать xargs. В зависимости от вашей конфигурации попробуйте 2, 3, 4, 5, ... пока не найдете оптимальное число. Это предполагает наличие нескольких входных файлов и НЕТ отдельных файлов, которые намного больше, чем все другие файлы.

Обратите внимание, что добавлено 'fflu sh', чтобы строки не были разделены. Это окажет некоторое негативное влияние на производительность, но это необходимо, если вы хотите объединить отдельные входные файлы в единый набор выходных файлов. Эту проблему можно решить, разделив каждый файл, а затем объединив объединенные файлы.

#! /bin/sh
pushd "_test2" > /dev/null
ls * | xargs --max-procs=4 -L1 awk '
    BEGIN {
        FS=":"
    }
    {
        gsub(/^[ \t"'\'']+|[ \t"'\'']+$/, "")
        $0=gensub("[,|;: \t]+",":",1,$0)
        if (NF>1 && $1 ~ /^[[:alnum:]_.+-]+@[[:alnum:]_.-]+\.[[:alnum:]]+$/ && $0 ~ /^[\x00-\x7F]*$/)
        {
            Fpath=tolower(substr($1,1,2))
            Fpath=gensub("[^[:alnum:]]","_","g",Fpath)
            print $0 >> Fpath
            fflush(Fpath)
        }
        else
            print $0 >> "errorfile"
            fflush("errorfile")

    }' "$FILE"
popd > /dev/null

С практической точки зрения вам может потребоваться создать сценарий awk, например, split.awk

#! /usr/bin/awk -f -
BEGIN {
    FS=":"
}
{
    gsub(/^[ \t"'\'']+|[ \t"'\'']+$/, "")
    $0=gensub("[,|;: \t]+",":",1,$0)
    if (NF>1 && $1 ~ /^[[:alnum:]_.+-]+@[[:alnum:]_.-]+\.[[:alnum:]]+$/ && $0 ~ /^[\x00-\x7F]*$/)
    {
        Fpath=tolower(substr($1,1,2))
        Fpath=gensub("[^[:alnum:]]","_","g",Fpath)
        print $0 >> Fpath
    }
    else
        print $0 >> "errorfile"
}

И тогда «основной» код будет выглядеть, как показано ниже, проще управлять.

xargs --max-procs=4 -L1 awk -f split.awk
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...