SED или AWK заменить все шаблонами из другого файла

Я пытаюсь заменить шаблон с помощью сценария SED, но он не работает должным образом

sample_content.txt

288Y2RZDBPX1000000001dhana
JP2F64EI1000000002d
EU9V3IXI1000000003dfg1000000001dfdfds
XATSSSSFOO4dhanaUXIBB7TF71000000004adf
10Q1W4ZEAV18LXNPSPGRTTIDHBN1000000005egw

узоры.txt

1000000001 9000000003
1000000002 2000000001
1000000003 3000000001
1000000004 4000000001
1000000005 5000000001

Ожидаемый результат

288Y2RZDBPX9000000003dhana
JP2F64EI2000000001d
EU9V3IXI3000000001dfg9000000003dfdfds
XATSSSSFOO4dhanaUXIBB7TF74000000001adf
10Q1W4ZEAV18LXNPSPGRTTIDHBN5000000001egw

Я могу обойтись одной заменой SED, например

sed  's/1000000001/1000000003/g' sample_content.txt

Примечание.

  • Соответствующий шаблон не находится в фиксированном положении.
  • Одна строка может иметь несколько совпадающих значений для замены в sample_content.txt.
  • В файлах Sample_content.txt и Patterns.txt > 1 миллиона записей.

Ссылка на прикрепленный файл: https://drive.google.com/open?id=1dVzivKMirEQU3yk9KfPM6iE7tTzVRdt_

Может ли кто-нибудь предложить, как этого добиться, не влияя на производительность?

Обновлено 11 февраля 2018 г.

После анализа реального файла я только что получил подсказку, что в 30 и 31 позиции есть значение grade. Что помогает, где и во всем нам нужно применить замену.
Если класс AB, замените 10-значный номер телефона на 41–50 и 101–110
Если класс BC, замените 10-значный номер телефона на 11-20, 61-70 и 151-160
Если оценка DE, замените 10-значный номер телефона на 1- 10, 71–80, 151–160 и 181–190

Таким образом, я вижу 50 уникальных оценок для 2 миллионов записей образцов.

{   grade=substr($0,110,2)} // identify grade
{ 
    if (grade == "AB") {
        print substr($0,41,10) ORS substr($0,101,10)
    } else if(RT == "BC"){
        print substr($0,11,10) ORS substr($0,61,10) ORS substr($0,151,10) 
    }

    like wise 50 coiditions
}

Могу ли я узнать, целесообразен ли этот подход или какой-либо другой лучший подход?


person Dhanabalan    schedule 10.02.2018    source источник
comment
re: > 1 Million records && without affecting performance удачи   -  person mpapec    schedule 10.02.2018
comment
@ Сухой27, я полностью поддерживаю тебя в том же.   -  person RavinderSingh13    schedule 10.02.2018
comment
Оба решения, которые вы получили до сих пор, применяли каждую замену ко всей каждой строке ввода, а не к оставшейся части строки ввода после того, как были сделаны предыдущие замены, поэтому, если, например, sample_content.txt содержит xay и Pattern.txt включает a b и b c, то инструменты выведут xcy - соответствует ожидаемому результату или он должен быть xby? В зависимости от того, что верно, вы должны включить случай, который проверяет это, в ваш пример ввода.   -  person Ed Morton    schedule 10.02.2018
comment
Дханабалана, см. отчеты Джорджа о тестировании NICE, и кажется, что мое второе решение НАМНОГО БЫСТРЕЕ, чем мое предыдущее решение. Пожалуйста, проверьте все решения и сообщите нам о своем опыте, чтобы мы все тоже могли учиться здесь, ура :)   -  person RavinderSingh13    schedule 11.02.2018
comment
@ RavinderSingh13 OP специально сказал Single line may have multiple matching value to replace in sample_content.txt, поэтому переход цикла после первого совпадения, хотя и быстрее, не приведет к ожидаемому результату. Я также думаю, что просто делать 1 замену для каждой соответствующей строки вместо замены всех вхождений каждой строки в каждой строке (как это делают оба ваших скрипта с использованием sub() вместо gsub()) неправильно.   -  person Ed Morton    schedule 11.02.2018
comment
@EdMorton, конечно, Эд, сэр, я понял, вы правы.   -  person RavinderSingh13    schedule 11.02.2018
comment
Было бы лучше, может быть, не принимать ответ и позволить появиться большему количеству ответов. По мнению mt, все ответы работают не очень хорошо. Некоторые из них быстрее, чем другие, но даже самому быстрому потребуются часы для файла из 50 000 строк.   -  person George Vasiliou    schedule 11.02.2018
comment
@EdMorton Извините за задержку, Эд, сэр. Можно предположить, что в реальном сценарии такой закономерности не будет. Если приходит a = b, то b больше не придет на сторону LHS. Итак, как только мы заменили xay на xby, мы можем рассмотреть напоминание об этой строке для дальнейшей замены, поскольку одна строка будет иметь несколько совпадающих значений для замены.   -  person Dhanabalan    schedule 11.02.2018


Ответы (4)


Контрольные показатели для дальнейшего использования

Тестовая среда:

Используя файлы примеров patterns.txt с 50 000 строк и contents.txt также с 50 000 строк.

Все строки из patterns.txt загружаются во все решения, но проверяются только первые 1000 строк из contents.txt.

Тестируемый ноутбук оснащен двухъядерным 64-разрядным процессором Intel(R) Celeron(R) N3050 с тактовой частотой 2,16 ГГц, 4 ГБ ОЗУ, 64-разрядной версией Debian 9. Тестирование, gnu sed 4.4 и gnu awk 4.1.4

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

Полученные результаты:

<сильный>1. RavinderSingh13 1-е решение awk

$ time awk 'FNR==NR{a[$1]=$2;next}   {for(i in a){match($0,i);val=substr($0,RSTART,RLENGTH);if(val){sub(val,a[i])}};print}' patterns.txt  <(head -n 1000 contents.txt) >newcontents.txt

real    19m54.408s
user    19m44.097s
sys 0m1.981s

<сильный>2. Решение EdMorton 1st awk

$ time awk 'NR==FNR{map[$1]=$2;next}{for (old in map) {gsub(old,map[old])}print}' patterns.txt <(head -n1000 contents.txt) >newcontents.txt

real    20m3.420s
user    19m16.559s
sys 0m2.325s

<сильный>3. Sed (мой sed) решение

$ time sed -f <(printf 's/%s/%s/g\n' $(<patterns.txt)) <(head -n 1000 contents.txt) >newcontents.txt

real    1m1.070s
user    0m59.562s
sys 0m1.443s

<сильный>4. Решение Cyrus sed

$ time sed -f <(sed -E 's|(.*) (.*)|s/\1/\2/|g' patterns.txt) <(head -n1000 contents.txt) >newcontents.txt

real    1m0.506s
user    0m59.871s
sys 0m1.209s

<сильный>5. RavinderSingh13 2-е решение awk

$ time awk 'FNR==NR{a[$1]=$2;next}{for(i in a){match($0,i);val=substr($0,RSTART,RLENGTH);if(val){sub(val,a[i]);print;next}};}1' patterns.txt  <(head -n 1000 contents.txt) >newcontents.txt

real    0m25.572s
user    0m25.204s
sys     0m0.040s

Для небольшого количества входных данных, например 1000 строк, решение awk кажется хорошим. Давайте сделаем еще один тест с 9000 строками, на этот раз, чтобы сравнить производительность

6.RavinderSingh13 2-е решение awk с 9000 строками

$ time awk 'FNR==NR{a[$1]=$2;next}{for(i in a){match($0,i);val=substr($0,RSTART,RLENGTH);if(val){sub(val,a[i]);print;next}};}1' patterns.txt  <(head -9000 contents.txt) >newcontents.txt

real    22m25.222s
user    22m19.567s
sys      0m2.091s

<сильный>7. Решение Sed на 9000 строк

$ time sed -f <(printf 's/%s/%s/g\n' $(<patterns.txt)) <(head -9000 contents.txt) >newcontents.txt

real    9m7.443s
user    9m0.552s
sys     0m2.650s

<сильный>8. Параллельное решение Seds на 9000 строк

$ cat sedpar.sh
s=$SECONDS
sed -f <(printf 's/%s/%s/g\n' $(<patterns.txt)) <(head -3000 contents.txt) >newcontents1.txt &
sed -f <(printf 's/%s/%s/g\n' $(<patterns.txt)) <(tail +3001 contents.txt |head -3000) >newcontents2.txt &
sed -f <(printf 's/%s/%s/g\n' $(<patterns.txt)) <(tail +6001 contents.txt |head -3000) >newcontents3.txt &
wait
cat newcontents1.txt newcontents2.txt newcontents3.txt >newcontents.txt && rm -f newcontents1.txt newcontents2.txt newcontents3.txt
echo "seconds elapsed: $(($SECONDS-$s))"

$ time ./sedpar.sh
seconds elapsed: 309

real    5m16.594s
user    9m43.331s
sys     0m4.232s

Разделение задачи на большее количество команд, таких как три параллельных sed, может ускорить процесс.

Для тех, кто хотел бы повторить тесты на своем ПК, вы можете скачать файлы contents.txt и patterns.txt либо по ссылкам OP, либо с моего github:

contents.txt

patterns.txt

person George Vasiliou    schedule 10.02.2018
comment
Спасибо, Джордж, что поделился тем же. Не могли бы вы еще раз протестировать мое второе решение и опубликовать его здесь, поскольку у меня не так много файлов строк, которые я не мог бы проверить, сэр, буду вам благодарен. - person RavinderSingh13; 10.02.2018
comment
прикольно, у меня вообще эта ссылка почему-то не работает. Спасибо еще раз. - person RavinderSingh13; 10.02.2018
comment
Спасибо, Джордж, это потому, что как можно скорее я нахожу совпадение внутри массива (a) и выхожу из него, поэтому оно НЕ будет проходить по всему массиву :) Я рад, что смог сделать его более эффективным. БОЛЬШОЕ СПАСИБО за проверку, сэр. - person RavinderSingh13; 11.02.2018
comment
Большое спасибо всем. Впервые изучаю эти скрипты. Много знаний, полученных от них. Спасибо еще раз. - person Dhanabalan; 11.02.2018
comment
@ RavinderSingh13 Кажется, ваш awk быстр для небольших входных файлов. Если просмотреть 9000 строк входных файлов, то результаты будут другими. См. обновленный тест. - person George Vasiliou; 11.02.2018
comment
@Dhanabalan Ознакомьтесь с параллельным решением seds - person George Vasiliou; 11.02.2018
comment
Все ли сценарии выдавали один и тот же результат? Почему вы не попробовали второе решение в моем ответе - то, которое, как я утверждал, будет быстрее? Кроме того, крайне трудно поверить, что скрипт Ravinders, который выполняет 3 вызова функций в строке, будет быстрее, чем даже мой первый скрипт, который делает один - вы задали время 3-го выполнения, чтобы устранить проблемы с кэшированием? - person Ed Morton; 11.02.2018
comment
@EdMorton Я попробовал ваш второй скрипт, но по какой-то причине мой ноутбук плохо завис ... мне пришлось полностью перезагрузить машину (выключить питание). Я попробую еще раз. Я использую для запуска всех сценариев три раза. - person George Vasiliou; 11.02.2018
comment
Возможно, строка регулярного выражения становится слишком большой, меня это беспокоило. Вы сравнивали выходные файлы друг с другом, чтобы убедиться, что все скрипты выдают один и тот же результат? Также для ваших параллельных сценариев sed вам следует добавить шаг, который объединяет выходные файлы в 1 и убирает, удаляя временные выходные файлы, чтобы его вывод был таким же, как и другие. Также имеет смысл делать это в цикле, а не жестко кодировать несколько сценариев, чтобы это было реалистичным подходом. Спасибо за предоставленные тайминги. - person Ed Morton; 11.02.2018
comment
P.S. Я спросил о 3-м тайминге, потому что просто не вижу способа, которым match($0,i);val=substr($0,RSTART,RLENGTH);if(val){sub(val,a[i])} мог бы быть быстрее, чем один sub(val,a[i]), но я не могу придумать ничего, кроме кэширования, которое могло бы объяснить полученные вами результаты! - person Ed Morton; 11.02.2018
comment
@EdMorton Использование cat для таких небольших файлов (по 3000 строк в каждом) на самом деле не влияет на производительность параллельного сценария sed. Я обновил скрипт. Я не проверял результаты (файл newcontents.txt) ни в одном из решений. - person George Vasiliou; 11.02.2018
comment
Однако этот cat не будет выполнять эту работу, поскольку он будет отображать файлы в алфавитном порядке, а не в числовом. Я имею в виду, что для общего, пригодного для использования решения требуется больше работы, чем просто запуск sed несколько раз. - person Ed Morton; 11.02.2018
comment
@EdMorton Это правда, что ваш второй скрипт превращает мой ноутбук в кирпич. Извиняюсь. Я загружаю файлы OP на github (см. ссылки в этом посте); может быть, вы можете сделать тест самостоятельно. - person George Vasiliou; 11.02.2018
comment
Думаю, мне придется разделить это RE на массив все еще больших, но меньших RE. Ну что ж. ОП уже выбрал ответ и не отвечает на вопросы (см. мой комментарий под вопросом), поэтому пришло время двигаться дальше, как он, по-видимому, сделал .... еще раз спасибо за проведение тестов времени. - person Ed Morton; 11.02.2018

Попробуйте этот. Должно быть быстро.

$ sed -f <(printf 's/%s/%s/g\n' $(<patterns.txt)) contents.txt

Это форматирует данные `patterns.txt, как показано ниже, без фактического изменения реального содержимого Patterns.txt:

$ printf 's/%s/%s/g\n' $(<patterns.txt)
s/1000000001/9000000003/g
s/1000000002/2000000001/g
s/1000000003/3000000001/g
s/1000000004/4000000001/g
s/1000000005/5000000001/g

Все вышеперечисленное затем дается с заменой процесса <(...) на простой sed в виде файла сценария с использованием
sed -f переключателя = читать команды sed из файла

$ sed -f <(printf 's/%s/%s/g\n' $(<patterns.txt)) contents.txt
288Y2RZDBPX9000000003dhana
JP2F64EI2000000001d
EU9V3IXI3000000001dfg9000000003dfdfds
XATSSSSFOO4dhanaUXIBB7TF74000000001adf
10Q1W4ZEAV18LXNPSPGRTTIDHBN5000000001egw
person George Vasiliou    schedule 10.02.2018
comment
@Dhanabalan: Может быть, так быстрее: sed -f <(sed -E 's|(.*) (.*)|s/\1/\2/|g' patterns.txt) sample_content.txt - person Cyrus; 10.02.2018

Не могли бы вы попробовать подписаться на awk и дайте мне знать, если это поможет вам.

Решение 1.

awk 'FNR==NR{a[$1]=$2;next}   {for(i in a){match($0,i);val=substr($0,RSTART,RLENGTH);if(val){sub(val,a[i])}};print}' patterns.txt  sample_content.txt

Вывод будет следующим.

288Y2RZDBPX9000000003dhana
JP2F64EI2000000001d
EU9V3IXI3000000001dfg9000000003dfdfds
XATSSSSFOO4dhanaUXIBB7TF74000000001adf
10Q1W4ZEAV18LXNPSPGRTTIDHBN5000000001egw

Объяснение решения 1 : Добавление объяснения здесь:

awk '
FNR==NR{                           ##FNR==NR is a condition which will be TRUE when only first Input_file patterns.txt is being read.
                                   ##FNR and NR both represents line number of Input_file(s) where FNR value will be RESET when a new Input_file is getting read on the other hand NR value will be keep increasing till all Input_file(s) read.
  a[$1]=$2;                        ##creating an array a whose index is first field of line and value is 2nd field of current line.
  next                             ##next will skip all further statements for now.
}
{
for(i in a){                       ##Starting a for loop which traverse through array a all element.
  match($0,i);                     ##Using match function of awk which will try to match index if array a present in variable i.
  val=substr($0,RSTART,RLENGTH);   ##Creating a variable named val which contains the substring of current line substring starts from value of variable RSTART till RLENGTH value.
  if(val){                         ##Checking condition if variable val is NOT NULL then do following:
    sub(val,a[i])}                 ##using sub function of awk to substitute variable val value with array a value of index i.
};
  print                            ##Using print here to print the current line either changed or not changed one.
}
' patterns.txt  sample_content.txt ##Mentioning the Input_file(s) name here.

Решение 2-е: без постоянного перехода к массиву, как первое решение, выходящее из массива, когда совпадение найдено следующим образом:

awk '
FNR==NR{                           ##FNR==NR is a condition which will be TRUE when only first Input_file patterns.txt is being read.
                                   ##FNR and NR both represents line number of Input_file(s) where FNR value will be RESET when a new Input_file is getting read on the other hand NR value will be keep increasing till all Input_file(s) read.
  a[$1]=$2;                        ##creating an array a whose index is first field of line and value is 2nd field of current line.
  next                             ##next will skip all further statements for now.
}
{
for(i in a){                       ##Starting a for loop which traverse through array a all element.
  match($0,i);                     ##Using match function of awk which will try to match index if array a present in variable i.
  val=substr($0,RSTART,RLENGTH);   ##Creating a variable named val which contains the substring of current line substring starts from value of variable RSTART till RLENGTH value.
  if(val){                         ##Checking condition if variable val is NOT NULL then do following:
    sub(val,a[i]);print;next}                 ##using sub function of awk to subsitute variable val value with array a value of index i.
};
}
1
' patterns.txt  sample_content.txt ##Mentioning the Input_file(s) name here.
person RavinderSingh13    schedule 10.02.2018
comment
Ожидаемый результат идет хорошо, но для его завершения требуется слишком много времени. - person Dhanabalan; 10.02.2018
comment
Я приложил образец входного файла drive.google.com/open?id=1dVzivKMirEQU3yk9KfPM6iE7tTzVRdt_. - person Dhanabalan; 10.02.2018
comment
@Dhanabalan, давайте посчитаем, сколько времени это займет один раз, и тогда мы сможем увидеть больше, что мы можем сделать больше, дайте нам знать об этом. - person RavinderSingh13; 10.02.2018
comment
Он до сих пор работает :( - person Dhanabalan; 10.02.2018
comment
@Dhanabalan, пожалуйста, наберитесь терпения и протестируйте оба решения и запишите их время, добавив перед ними команду time. Дайте нам знать, как это происходит. Мы не можем ожидать, что миллионы строк будут обработаны за секунды, приятель. - person RavinderSingh13; 10.02.2018
comment
Это намного сложнее, чем необходимо (вы начинаете с i, а затем используете match()+substr(), чтобы получить val, тогда как val всегда будет идентично i, а затем вы делаете sub(), чтобы найти ту же строку, на которой вы только что сделали match()!). См. первый сценарий, который я разместил на stackoverflow.com/a/48720400/1745001 для простой реализации этого подхода. Ваш также потерпит неудачу, если одна и та же строка появится несколько раз в 1 строке, поскольку она заменит только первое вхождение каждой строки. - person Ed Morton; 10.02.2018

Простой подход:

$ cat tst.awk
NR==FNR {
    map[$1] = $2
    next
}
{
    for (old in map) {
        gsub(old,map[old])
    }
    print
}

$ awk -f tst.awk patterns.txt sample_content.txt
288Y2RZDBPX9000000003dhana
JP2F64EI2000000001d
EU9V3IXI3000000001dfg9000000003dfdfds
XATSSSSFOO4dhanaUXIBB7TF74000000001adf
10Q1W4ZEAV18LXNPSPGRTTIDHBN5000000001egw

Так же, как и другие решения, опубликованные до сих пор, это применяет каждую замену ко всей строке, и, таким образом, если файл sample_content.txt содержит xay, а шаблоны.txt включает a b и b c, тогда инструменты будут выводить xcy, а не xby.

В качестве альтернативы вы можете попробовать это:

$ cat tst.awk
NR==FNR {
    map[$1] = $2
    re = re sep $1
    sep = "|"
    next
}
{
    head = ""
    tail = $0
    while ( match(tail,re) ) {
        head = head substr(tail,1,RSTART-1) map[substr(tail,RSTART,RLENGTH)]
        tail = substr(tail,RSTART+RLENGTH)
    }
    print head tail
}

$ awk -f tst.awk patterns.txt sample_content.txt
288Y2RZDBPX9000000003dhana
JP2F64EI2000000001d
EU9V3IXI3000000001dfg9000000003dfdfds
XATSSSSFOO4dhanaUXIBB7TF74000000001adf
10Q1W4ZEAV18LXNPSPGRTTIDHBN5000000001egw

Такой подход имеет несколько преимуществ:

  1. Он выведет xby (это то, что, как я подозреваю, вам действительно нужно, если возникнет такая ситуация) в случае, о котором я упоминал выше.
  2. Он выполняет столько сравнений регулярных выражений на строку sample_content.txt, сколько может совпадать, вместо 1 на строку Patterns.txt для каждой строки sample_content.txt.
  3. Он работает только с тем, что осталось от строки после предыдущей замены, поэтому тестируемая строка продолжает сжиматься.
  4. Он не изменяет $0, поэтому awk не нужно перекомпилировать и повторно разбивать эту запись при каждой подстановке.

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

person Ed Morton    schedule 10.02.2018