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 и шаблоны.txt имеет > 1 млн записей


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



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

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



После анализа реального файла я только что получил подсказку, что существуетоценка значение на30-й и 31-й позиции . Который помогает там, где и всем нам нужно применить замену.

Если класс AB , то замените 10-значный номер телефона на 41-50 и 101-110

Если класс BC , то замените 10-значный номер телефона на 11-20, 61-70 и 151-160

Если grade 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
}


Могу ли я узнать, является ли этот подход целесообразным или любой другой лучший подход?
613   4  

4 ответов:

Ориентиры для будущего использования

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

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

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

Тестовый ноутбук оснащен двухъядерным 64-битным процессором Intel(R) Celeron(R) CPU N3050 @ 2.16 GHz, 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 Solution

$ 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 (my sed) solution

$ 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. Решение Сайруса Седа

$ 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:

Содержание.txt

Узоры.txt

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

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

Это форматирует данные ' паттернов.txt, как Беллоу, фактически не меняя паттернов.реальное содержание 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 switch = чтение команд sed из файла

$ sed -f <(printf 's/%s/%s/g\n' $(<patterns.txt)) contents.txt
288Y2RZDBPX9000000003dhana
JP2F64EI2000000001d
EU9V3IXI3000000001dfg9000000003dfdfds
XATSSSSFOO4dhanaUXIBB7TF74000000001adf
10Q1W4ZEAV18LXNPSPGRTTIDHBN5000000001egw

Не могли бы вы, пожалуйста, попробовать следовать 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.

Простой подход таков:

$ 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 на линии узоров.txt для каждой строки sample_content.txt
  3. он работает только с тем, что осталось от строки после предыдущей замены, поэтому тестируемая строка продолжает сжиматься
  4. он не изменяется $0, и поэтому awk не нужно перекомпилировать и перепрофилировать эту запись с каждой подстановкой.

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

Comments

    Ничего не найдено.