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-Feb-2018

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

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

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

    like wise 50 coiditions
}

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

4 ответов


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

тестовой среде:

использование файлов образцов patterns.txt С 50 000 строк и contents.txt также с 50 000 строк.

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

тестовый ноутбук оснащен двухъядерным 64-битным процессором Intel (R) Celeron (R) CPU N3050 @ 2.16 GHz, 4 GB RAM, Debian 9 64bit Testing , gnu sed 4.4 и gnu awk 4.1.4

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

результаты:

1. RavinderSingh13 1-е решение awk

$ time awk 'FNR==NR{a[]=;next}   {for(i in a){match(,i);val=substr(,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 1-й на awk решение

$ time awk 'NR==FNR{map[]=;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///|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[]=;next}{for(i in a){match(,i);val=substr(,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 2nd awk решение с 9000 строк

$ time awk 'FNR==NR{a[]=;next}{for(i in a){match(,i);val=substr(,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. Параллельно с Самошифрованием решение с 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 как bellow без фактического изменения шаблонов.тхт реальное содержание:

$ 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[]=;next}   {for(i in a){match(,i);val=substr(,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[]=;                        ##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(,i);                     ##Using match function of awk which will try to match index if array a present in variable i.
  val=substr(,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[]=;                        ##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(,i);                     ##Using match function of awk which will try to match index if array a present in variable i.
  val=substr(,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[] = 
    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.тхт, содержащий xay и узоры.txt, в том числе a b и b c, тогда инструменты будут выводить xcy, а не xby.

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

$ cat tst.awk
NR==FNR {
    map[] = 
    re = re sep 
    sep = "|"
    next
}
{
    head = ""
    tail = 
    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. это не меняет и поэтому awk не нужно перекомпилировать и повторно разбить это запись с каждым subsitution.

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