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
}
Могу ли я узнать, является ли этот подход целесообразным или любой другой лучший подход?
4 ответов:
Ориентиры для будущего использования
Тестовая среда:
Используя ваши файлы примеров
patterns.txtс 50 000 строк иcontents.txtтакже с 50 000 строк.Все строки из
Тестовый ноутбук оснащен двухъядерным 64-битным процессором Intel(R) Celeron(R) CPU N3050 @ 2.16 GHz, 4 ГБ оперативной памяти, 64-битным тестированием Debian 9,patterns.txtзагружаются во все решения, но рассматриваются только первые 1000 строк изcontents.txt.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.981s2. 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.325s3. 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.443s4. Решение Сайруса Седа
$ 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.209s5. 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.091s7. Решение 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.650s8. Параллельное решение 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:
Попробуйте вот это . Должно быть быстро.
$ 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 -fswitch = чтение команд 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Этот подход имеет несколько преимуществ:
- Это будет выводить
xby(что, как я подозреваю, вы действительно хотите, если такая ситуация возник) в случае, о котором я упоминал выше- он выполняет столько же сравнений регулярных выражений на строку sample_content.txt, как мог сравниться, а не 1 на линии узоров.txt для каждой строки sample_content.txt
- он работает только с тем, что осталось от строки после предыдущей замены, поэтому тестируемая строка продолжает сжиматься
- он не изменяется
$0, и поэтому awk не нужно перекомпилировать и перепрофилировать эту запись с каждой подстановкой.Так и должно быть. намного быстрее, чем исходный сценарий, предполагающий регулярное выражение, построенное из шаблонов.txt не настолько огромен, что вызывает снижение производительности только из-за его размера.
Comments