Как выполнить минимальный дисковый ввод-вывод при «поиске и замене» из 20 тыс. Терминов в 100 текстовых файлах (по 50 тыс. Строк в каждом) - PullRequest
2 голосов
/ 15 мая 2019

Я хотел бы выполнить поиск и замену примерно 100 файлов данных с неструктурированным текстом.Каждый из файлов имеет размер около 50 МБ и 50 тыс. Строк.Есть около 20 тысяч терминов для поиска и замены, которые хранятся в другом файле;'term_list.csv', файл формата CSV с тремя столбцами COL1, COL2 и COL3.Мне нужно искать слова в COL1 и COL2 файла «term_list.csv» в каждом из 100 файлов данных и заменять соответствующим словом в COL3, если любое из слов найдено.

Имея базовые знания в области написания сценариев оболочки, я написал следующий сценарий оболочки с использованием AWK / SED для цикла.Он читает строки по одной из 20k строк 'term_list.csv' и ищет COL1 и COL2 в каждом из 100 файлов, и, если он найден, заменяется на COL3.

for DATA_FILE in $(ls text_data_file_*.csv) #Data files (100 files) with 50k lines; contain terms in COL1 and COL2 of terms_list.csv
do
   while read -r line;  
       do
           x=$(echo $line | awk -F',' '{print $1}'); \
           y=$(echo $line | awk -F',' '{print $2}'); \
           z=$(echo $line | awk -F',' '{print $3}'); \
           echo "File: " $DATA_FILE " x: "$x "|" "y: "$y "|" "z: "$z ; \
           sed -i "s/$x/$z/;s/$y/$z/g" $DATA_FILE
       done < terms_list.csv #20k lines in CSV format; each with search terms COL1,COL2, and replace term COL3
done

Я уверен, что для выполнения этой задачи есть лучший / эффективный код, чем приведенный выше, поскольку для этого требуется много операций чтения / записи на диске.Есть предложения по улучшению?Если есть более подходящие инструменты (perl / python) для этой задачи, не могли бы вы дать мне несколько советов / предложений, на которые стоит обратить внимание?

Ниже приведены примеры данных для обоих файлов:

  1. 'text_data_file_0001.csv': один из 100 файлов данных 'text_data_file_0001.csv' содержит неструктурированные данныекак ниже, которые содержат «TermFull» и «TermAbbreviated» среди текста.[размер каждого файла составляет около 50 МБ и 50 тыс. строк]

    ID000001,Mangifera indica, commonly known as mango, is a species of flowering plant in the sumac and poison ivy family Anacardiaceae. M. indica is a popular fruit in India. 
    ID000002,Oryza sativa, commonly known as Asian rice, is the plant species most commonly referred to in English as rice. O. sativa contains two major subspecies: the sticky, short-grained japonica or sinica variety, and the nonsticky, long-grained indica rice variety.
    
  2. файл «term_list.csv»: поисковые термины «TermFull» и «TermAbbreviated» и заменяют термин «TermJoined 'хранятся в' term_list.csv 'содержит 20 тыс. Строк, как показано ниже

    TermFull,TermAbbreviated,TermJoined
    Mangifera indica,M. indica,Mangiferaindica
    Oryza sativa,O. sativa,Oryzasativa
    
  3. Требуемый выходной файл' text_data_file0001.csv 'такой же, как и ниже, с заменой' TermFull 'и' TermAbbreviated 'с «TermJoined»

    ID000001,Mangiferaindica, commonly known as mango, is a species of flowering plant in the sumac and poison ivy family Anacardiaceae. Mangiferaindica is a popular fruit in India. 
    ID000002,Oryzasativa, commonly known as Asian rice, is the plant species #most commonly referred to in English as rice. Oryzasativa contains two major subspecies: the sticky, short-grained japonica or sinica variety, and the nonsticky, long-grained indica rice variety.
    

Ответы [ 3 ]

4 голосов
/ 15 мая 2019

Вот полный подход (т. Е. Не требуется окружающий цикл оболочки или что-либо еще) с использованием GNU awk для редактирования «на месте»:

awk -i inplace -F, '
NR==FNR { if (NR>1) { map[$1]=$3; map[$2]=$3 } print; next }
{
    for (term in map) {
        gsub(term,map[term])
    }
    print
}
' terms_list.csv text_data_file_*.csv

Массаж для костюма. Если, похоже, ваш файл term_list может содержать метасхемы RE, например, вам следует подумать о том, хотите ли вы использовать их в регулярном выражении, как вы это делали в sed, и так мы делаем выше с gsub (), или используйте строковая операция, например, с index () и substr () вместо gsub (), а также способ обработки частичных совпадений и / или случаев, когда первая замена создает термин, которого ранее не существовало, который затем сопоставляется вторым замена и пр.

Я подозреваю, что что-то подобное (непроверенное) будет и гибким, и достаточно надежным для ваших нужд (оно, безусловно, будет гораздо более надежным, чем сценарий sed, который вы выполняли, и буквально на несколько порядков быстрее, чем ваш подход shell loop + sed). ):

awk -i inplace -F, '
NR==FNR {
    orig = $0

    if (NR > 1) {
        gsub(/[^^]/,"[&]",$1)
        gsub(/\^/,"\\^",$1)

        gsub(/[^^]/,"[&]",$2)
        gsub(/\^/,"\\^",$2)

        gsub(/&/,"\\&",$3)

        map["\\<"$1"\\>"] = $3
        map["\\<"$2"\\>"] = $3
    }

    print orig
    next
}
{
    for (term in map) {
        gsub(term,map[term])
    }
    print
}
' terms_list.csv text_data_file_*.csv

gsubs избегают любых мета-символов, поэтому каждый символ в оригинальном и замещающем тексте обрабатывается буквально, но затем мы добавляем границы слов вокруг всего каждого исходного термина, чтобы избежать частичных совпадений.

1 голос
/ 16 мая 2019

Вы можете использовать sed для создания сценария sed из terms_list.csv:

sed '1d;s/,/|/;s|,|/|;s|.*|s/&/g|' terms_list.csv

, который работает следующим образом:

1d           # Skip the first line
s/,/|/       # Replace the first comma with a pipe
s|,|/|       # Replace the second comma with a slash
s|.*|s/&/g|  # Wrap each line in s/ and /g

и имеет следующий вывод:

$ sed '1d;s/,/|/;s|,|/|;s|.*|s/&/g|' terms_list.csv
s/Mangifera indica|M. indica/Mangiferaindica/g
s/Oryza sativa|O. sativa/Oryzasativa/g

Теперь мы используем этот вывод для запуска sed -i (требуется GNU sed для редактирования на месте) для всех файлов, которые мы хотим изменить:

sed '1d;s/,/|/;s|,|/|;s|.*|s/&/g|' terms_list.csv | sed -i -Ef- text_data_file_*.csv
  • -E включаетрасширенные регулярные выражения, поэтому мы можем использовать | для чередования
  • -f- читает команды sed из стандартного ввода

Первая команда может быть сделана немного более устойчивойв терминах явных границ слов, чтобы избежать совпадений подстрок:

$ sed '1d;s/,/|/;s|,|)\\b/|;s|.*|s/\\b(&/g|' terms_list.csv
s/\b(Mangifera indica|M. indica)\b/Mangiferaindica/g
s/\b(Oryza sativa|O. sativa)\b/Oryzasativa/g

, где \b обозначает границу слова (также расширение GNU sed).


Если вход содержитрегулярные выражения метасимволы, мы должны избегать их всех, поэтому первая команда должна выглядеть примерно так:

sed '1d;s/[][*+{}()/\|&^$.?]/\\&/g;s/,/|/;s|,|)\\b/|;s|.*|s/\\b(&/g|' terms_list.csv

Важным дополнением здесь является первая подстановка, которая заботится о метасимволах, экранируя их обратной косой чертой:

s/[][*+{}()/\|&^$.?]/\\&/g

Так что для наихудшего случая scenario, где terms_list.csv содержит что-то вроде

a[abc]*x+\1{2}|-(o).^$?/\a,other,abc&\1def

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

s/\b(a\[abc\]\*x\+\\1\{2\}\|-\(o\)\.\^\$\?\/\\a|other)\b/abc\&\\1def/g

Есть случай, который все еще не охватывается этим: если входной файл CSVсодержит запятые в одном из полей, вам нужно прибегнуть к анализатору CSV для предварительной обработки.Запятые игнорируются при экранировании метасимволов регулярных выражений в третьем решении.

0 голосов
/ 24 мая 2019

ОБНОВЛЕНИЕ 2019-05-24:

После небольшого количества проб и ошибок мне понравился подход BenjaminW.Тем не менее, я также адаптировал подход для perl и обнаружил, что perl (v5.22.1) превзошел sed (GNU sed 4.2.2) в этом случае.(С очень похожим кодом; требуется в конце шаблона), perl был примерно в три раза быстрее, чем sed. Посмотрите выходные данные команды времени. Выводы fie имели аналогичные результаты подсчета слов) Я разместил sed и perlкоды ниже, которые работали для моей текущей потребности.

#!/bin/bash
##################################################
##terms_list.csv content: About 20k lines
#TermFull,TermAbbreviated,TermJoined
#Mangifera indica,M. indica,Mangiferaindica
#Oryza sativa,O. sativa,Oryzasativa

## SCRIPT 1: sed
substitution_with_sed(){
    #First generate the substitution pattern script
    sed '1d;s/[][*+{}()/\|&^$.?]/\\&/g;s/,/|/;s|,|)\\b/|;s|.*|s/\\b(&/g|' terms_list.csv > terms_list.sed
    #1d           # Skip the first line; which is the CSV header terms
    #s/,/|/       # Replace the first comma with a pipe
    #s|,|/|       # Replace the second comma with a slash
    #s|.*|s/&/g|  # Wrap each line in s/ and /g
    #s/[][*+{}()/\|&^$.?]/\\&/g #Escape any regex metacharacters with a backslash
    ##'terms_list.sed' file content
    #s/\b(Mangifera indica|M. indica)\b/Mangiferaindica/g
    #s/\b(Oryza sativa|O. sativa)\b/Oryzasativa/g

    for DATA_FILE in ./DIR/DATA_CSV_FILE*.csv; # About 100k DATA_CSV_FILE*.csv files
    do
        FILE="$(basename $DATA_FILE)"
        echo "Running SED on $DATA_FILE and $FILE"
        echo "sed -E -f terms_list.sed < $DATA_FILE > sed-out-$FILE"
        time sed -E -f terms_list.sed < $DATA_FILE > sed-out-$FILE
        #-E enables extended regular expressions so we can use | for alternation
        #-f- reads the sed commands from standard input
        # # real    25m55.369s
        # # user    25m54.976s
        # # sys     0m0.336s
    done
}

## SCRIPT 2: perl
substitution_with_perl(){

#First generate the substitution script
sed '1d;s/[][*+{}()/\|&^$.?]/\\&/g;s/,/|/;s|,|)\\b/|;s|.*|s/\\b(&/g;|' terms_list.csv > terms_list.perl
    ##'terms_list.perl' file content
    #s/\b(Mangifera indica|M. indica)\b/Mangiferaindica/g;
    #s/\b(Oryza sativa|O. sativa)\b/Oryzasativa/g;

    for DATA_FILE in ./DIR/DATA_CSV_FILE*.csv;
    do
        FILE="$(basename $DATA_FILE)"
        echo "Running PERL on $DATA_FILE and $FILE"
        echo "perl -p terms_list.perl < $DATA_FILE > perl-out-$FILE"
        time perl -p terms_list.perl < $DATA_FILE > perl-out-$FILE
        ## Read substitution pattern command from file with -p flag
        # # real    0m8.120s
        # # user    0m8.072s
        # # sys     0m0.044s
    done
}
#####################################################

##Call functions
substitution_with_sed
substitution_with_perl

#Raw data
#ID000001,Mangifera indica, commonly known as mango, is a species of flowering plant in the sumac and poison ivy family Anacardiaceae. M. indica is a popular fruit in India. 
#ID000002,Oryza sativa, commonly known as Asian rice, is the plant species most commonly referred to in English as rice. O. sativa contains two major subspecies: the sticky, short-grained #japonica or sinica variety, and the nonsticky, long-grained indica rice variety.

#Desired processed output data in 'sed-out-$FILE'/'perl-out-$FILE' file content
#ID000001,Mangiferaindica, commonly known as mango, is a species of flowering plant in the sumac and poison ivy family Anacardiaceae. Mangiferaindica is a popular fruit in India. 
#ID000002,Oryzasativa, commonly known as Asian rice, is the plant species most commonly referred to in English as rice. Oryzasativa contains two major subspecies: the sticky, short-grained japonica or sinica variety, and the nonsticky, long-grained indica rice variety.

@ EdMorton, @CharlesDuffy, @BenjaminW, еще раз спасибо за ваши комментарии и решения.Информация, которую вы предоставили, была чрезвычайно полезна для меня, и я многому научился за последнюю неделю.Я воспользовался вашими предложениями и кратким изложением / документом ниже для наивных программистов, подобных мне.

  1. Спасибо @EdMorton.Будьте осторожны с метасоставщиками в шаблоне замещения!Я имею .в моих данных, и это означает «все» в RegExp.Это нужно экранировать с помощью обратной косой черты.

  2. Благодаря @CharlesDuffy;При переборе множества элементов или строк с любым циклом в оболочке использование любого внешнего инструмента внутри этого цикла значительно снизит эффективность.Новый код ниже очень эффективен по сравнению с кодом выше.

  3. Благодаря @CharlesDuffy;в то время как IFS =, читайте -rxyz _;do прочитает CSV и назначит переменные.

  4. Благодаря @CharlesDuffy;echo "File: $ DATA_FILE x: $ x | y: $ y | z: $ z"; передача всего в echo в виде одной строки лучше, чем у меня в исходном коде выше.

Мне также интересно узнать о решении на Python, и я обновлю его, когда у меня будет рабочий код и некоторые тесты.

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