SED 或 AWK 全部替换为来自另一个文件的模式
SED or AWK replace all with patterns from another file
我正在尝试使用 SED 脚本进行模式替换,但它无法正常工作
sample_content.txt
288Y2RZDBPX1000000001dhana
JP2F64EI1000000002d
EU9V3IXI1000000003dfg1000000001dfdfds
XATSSSSFOO4dhanaUXIBB7TF71000000004adf
10Q1W4ZEAV18LXNPSPGRTTIDHBN1000000005egw
patterns.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 有超过 100 万条记录
文件附件 link: https://drive.google.com/open?id=1dVzivKMirEQU3yk9KfPM6iE7tTzVRdt_
谁能建议如何在不影响性能的情况下实现这一点?
2018 年 2 月 11 日更新
分析真实文件后,我得到一个提示,在 30 和第 31 位 处有一个 grade 值。这有助于我们在哪里和所有需要应用替换。
如果 等级 AB 则替换 41-50 和 101-110
处的 10 位数字 phone
如果 BC 则替换 11-20、61-70 和 151-160[=55= 处的 10 位数字 phone ]
如果 等级 DE 然后替换 1-10、71-80、151-160 和 181-190 处的 10 位数字 phone
像这样,我看到 50 个独特的等级 2 百万 个样本记录。
{ grade=substr([=14=],110,2)} // identify grade
{
if (grade == "AB") {
print substr([=14=],41,10) ORS substr([=14=],101,10)
} else if(RT == "BC"){
print substr([=14=],11,10) ORS substr([=14=],61,10) ORS substr([=14=],151,10)
}
like wise 50 coiditions
}
我可以知道这种方法是否可取或其他更好的方法吗?
能否请您尝试关注 awk
如果这对您有帮助,请告诉我。
解法一:
awk 'FNR==NR{a[]=;next} {for(i in a){match([=10=],i);val=substr([=10=],RSTART,RLENGTH);if(val){sub(val,a[i])}};print}' patterns.txt sample_content.txt
输出如下。
288Y2RZDBPX9000000003dhana
JP2F64EI2000000001d
EU9V3IXI3000000001dfg9000000003dfdfds
XATSSSSFOO4dhanaUXIBB7TF74000000001adf
10Q1W4ZEAV18LXNPSPGRTTIDHBN5000000001egw
第一个解决方案的解释:现在在这里也添加解释:
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([=12=],i); ##Using match function of awk which will try to match index if array a present in variable i.
val=substr([=12=],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([=13=],i); ##Using match function of awk which will try to match index if array a present in variable i.
val=substr([=13=],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.
试试这个。应该很快。
$ 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
switch = 从文件中读取 sed 命令
$ sed -f <(printf 's/%s/%s/g\n' $(<patterns.txt)) contents.txt
288Y2RZDBPX9000000003dhana
JP2F64EI2000000001d
EU9V3IXI3000000001dfg9000000003dfdfds
XATSSSSFOO4dhanaUXIBB7TF74000000001adf
10Q1W4ZEAV18LXNPSPGRTTIDHBN5000000001egw
简单的做法是:
$ 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.txt 包含 xay
和 patterns.txt,包括 a b
和 b c
,那么工具将输出 xcy
而不是 xby
。
或者你可以试试这个:
$ cat tst.awk
NR==FNR {
map[] =
re = re sep
sep = "|"
next
}
{
head = ""
tail = [=11=]
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 进行尽可能多的正则表达式比较,而不是每行 patterns.txt 对 sample_content.txt
的每一行进行 1 次匹配
- 它只对上一次替换后行的左边进行操作,因此被测试的字符串不断收缩
- 它不会改变
[=18=]
因此 awk 不必在每次替换时都重新编译和重新拆分该记录。
所以它应该比原始脚本 多 快,假设从 patterns.txt 构造的正则表达式不是太大以至于仅仅因为它的大小而导致性能下降。
以后参考的基准
测试环境:
使用您的示例文件 patterns.txt
50,000 行,contents.txt
也使用 50,000 行。
patterns.txt
中的所有行都加载到所有解决方案中,但仅检查 contents.txt
的前 1000 行。
测试笔记本电脑配备双核 64 位英特尔(R) 赛扬(R) CPU N3050 @ 2.16GHz,4 GB RAM,Debian 9 64 位测试,gnu sed 4.4
和 gnu awk 4.1.4
在所有情况下,输出都会发送到新文件,以避免在屏幕上打印数据的缓慢开销。
结果:
1. RavinderSingh13 第一个 awk 解决方案
$ time awk 'FNR==NR{a[]=;next} {for(i in a){match([=10=],i);val=substr([=10=],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 第一个 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 第二个 awk 解决方案
$ time awk 'FNR==NR{a[]=;next}{for(i in a){match([=14=],i);val=substr([=14=],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 9000 行的第二个 awk 解决方案
$ time awk 'FNR==NR{a[]=;next}{for(i in a){match([=15=],i);val=substr([=15=],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. 9000 行的 Sed 解决方案
$ 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 行的并行 Seds 解决方案
$ 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
将任务拆分为更多命令(例如三个并行 SEDS)似乎可以加快速度。
对于那些想在自己的 PC 上重复基准测试的人,您可以通过 OP 的链接或我的 github 下载文件 contents.txt
和 patterns.txt
:
我正在尝试使用 SED 脚本进行模式替换,但它无法正常工作
sample_content.txt
288Y2RZDBPX1000000001dhana
JP2F64EI1000000002d
EU9V3IXI1000000003dfg1000000001dfdfds
XATSSSSFOO4dhanaUXIBB7TF71000000004adf
10Q1W4ZEAV18LXNPSPGRTTIDHBN1000000005egw
patterns.txt
1000000001 9000000003
1000000002 2000000001
1000000003 3000000001
1000000004 4000000001
1000000005 5000000001
预期输出
288Y2RZDBPX9000000003dhana
JP2F64EI2000000001d
EU9V3IXI3000000001dfg9000000003dfdfds
XATSSSSFOO4dhanaUXIBB7TF74000000001adf
10Q1W4ZEAV18LXNPSPGRTTIDHBN5000000001egw
我可以像
sed 's/1000000001/1000000003/g' sample_content.txt
注:
- 匹配模式不在固定位置。
- 单行可能有多个匹配值替换sample_content.txt
- Sample_content.txt 和 patterns.txt 有超过 100 万条记录
文件附件 link: https://drive.google.com/open?id=1dVzivKMirEQU3yk9KfPM6iE7tTzVRdt_
谁能建议如何在不影响性能的情况下实现这一点?
2018 年 2 月 11 日更新
分析真实文件后,我得到一个提示,在 30 和第 31 位 处有一个 grade 值。这有助于我们在哪里和所有需要应用替换。
如果 等级 AB 则替换 41-50 和 101-110
处的 10 位数字 phone
如果 BC 则替换 11-20、61-70 和 151-160[=55= 处的 10 位数字 phone ]
如果 等级 DE 然后替换 1-10、71-80、151-160 和 181-190 处的 10 位数字 phone
像这样,我看到 50 个独特的等级 2 百万 个样本记录。
{ grade=substr([=14=],110,2)} // identify grade
{
if (grade == "AB") {
print substr([=14=],41,10) ORS substr([=14=],101,10)
} else if(RT == "BC"){
print substr([=14=],11,10) ORS substr([=14=],61,10) ORS substr([=14=],151,10)
}
like wise 50 coiditions
}
我可以知道这种方法是否可取或其他更好的方法吗?
能否请您尝试关注 awk
如果这对您有帮助,请告诉我。
解法一:
awk 'FNR==NR{a[]=;next} {for(i in a){match([=10=],i);val=substr([=10=],RSTART,RLENGTH);if(val){sub(val,a[i])}};print}' patterns.txt sample_content.txt
输出如下。
288Y2RZDBPX9000000003dhana
JP2F64EI2000000001d
EU9V3IXI3000000001dfg9000000003dfdfds
XATSSSSFOO4dhanaUXIBB7TF74000000001adf
10Q1W4ZEAV18LXNPSPGRTTIDHBN5000000001egw
第一个解决方案的解释:现在在这里也添加解释:
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([=12=],i); ##Using match function of awk which will try to match index if array a present in variable i.
val=substr([=12=],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([=13=],i); ##Using match function of awk which will try to match index if array a present in variable i.
val=substr([=13=],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.
试试这个。应该很快。
$ 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
switch = 从文件中读取 sed 命令
$ sed -f <(printf 's/%s/%s/g\n' $(<patterns.txt)) contents.txt
288Y2RZDBPX9000000003dhana
JP2F64EI2000000001d
EU9V3IXI3000000001dfg9000000003dfdfds
XATSSSSFOO4dhanaUXIBB7TF74000000001adf
10Q1W4ZEAV18LXNPSPGRTTIDHBN5000000001egw
简单的做法是:
$ 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.txt 包含 xay
和 patterns.txt,包括 a b
和 b c
,那么工具将输出 xcy
而不是 xby
。
或者你可以试试这个:
$ cat tst.awk
NR==FNR {
map[] =
re = re sep
sep = "|"
next
}
{
head = ""
tail = [=11=]
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 进行尽可能多的正则表达式比较,而不是每行 patterns.txt 对 sample_content.txt 的每一行进行 1 次匹配
- 它只对上一次替换后行的左边进行操作,因此被测试的字符串不断收缩
- 它不会改变
[=18=]
因此 awk 不必在每次替换时都重新编译和重新拆分该记录。
所以它应该比原始脚本 多 快,假设从 patterns.txt 构造的正则表达式不是太大以至于仅仅因为它的大小而导致性能下降。
以后参考的基准
测试环境:
使用您的示例文件 patterns.txt
50,000 行,contents.txt
也使用 50,000 行。
patterns.txt
中的所有行都加载到所有解决方案中,但仅检查 contents.txt
的前 1000 行。
测试笔记本电脑配备双核 64 位英特尔(R) 赛扬(R) CPU N3050 @ 2.16GHz,4 GB RAM,Debian 9 64 位测试,gnu sed 4.4
和 gnu awk 4.1.4
在所有情况下,输出都会发送到新文件,以避免在屏幕上打印数据的缓慢开销。
结果:
1. RavinderSingh13 第一个 awk 解决方案
$ time awk 'FNR==NR{a[]=;next} {for(i in a){match([=10=],i);val=substr([=10=],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 第一个 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 第二个 awk 解决方案
$ time awk 'FNR==NR{a[]=;next}{for(i in a){match([=14=],i);val=substr([=14=],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 9000 行的第二个 awk 解决方案
$ time awk 'FNR==NR{a[]=;next}{for(i in a){match([=15=],i);val=substr([=15=],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. 9000 行的 Sed 解决方案
$ 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 行的并行 Seds 解决方案
$ 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
将任务拆分为更多命令(例如三个并行 SEDS)似乎可以加快速度。
对于那些想在自己的 PC 上重复基准测试的人,您可以通过 OP 的链接或我的 github 下载文件 contents.txt
和 patterns.txt
: