我想用非结构化文本对大约100个数据文件进行“搜索和替换”。每个文件的大小约为50 MB,行数为50k。大约有20k个术语可以进行搜索和替换,它们存储在另一个文件中;“terms _list.csv”是一个CSV格式文件,包含COL1、COL2和COL3三列。我需要在100个数据文件中的COL1和COL2中搜索'terms_list.csv‘的单词,并在COL3中用相应的单词替换,如果找到任何一个单词。
在掌握了shell脚本的基本知识之后,我使用AWK/SED循环编写了以下shell脚本。它从20k行“terms_list.csv”逐行读取,并在100个文件中搜索COL1和COL2,如果找到,将替换为COL3。
for DATA_FILE in $(ls text_data_file_*.csv) #Data files (100 files) with 50k lines; contain terms in COL1 and COL2 of terms_list.csv
do
while read -r line;
do
x=$(echo $line | awk -F',' '{print $1}'); \
y=$(echo $line | awk -F',' '{print $2}'); \
z=$(echo $line | awk -F',' '{print $3}'); \
echo "File: " $DATA_FILE " x: "$x "|" "y: "$y "|" "z: "$z ; \
sed -i "s/$x/$z/;s/$y/$z/g" $DATA_FILE
done < terms_list.csv #20k lines in CSV format; each with search terms COL1,COL2, and replace term COL3
done我确信有一个比上面更好/更有效的代码来完成任务,因为这需要大量的磁盘读/写。有什么改进的建议吗?如果有更好的工具(perl/python)来完成这项任务,您能给我一些建议/指导吗?
以下是这两个文件的示例数据:
发布于 2019-05-15 22:28:16
您可以使用sed从terms_list.csv构建sed脚本。
sed '1d;s/,/|/;s|,|/|;s|.*|s/&/g|' terms_list.csv它的工作如下:
1d # Skip the first line
s/,/|/ # Replace the first comma with a pipe
s|,|/| # Replace the second comma with a slash
s|.*|s/&/g| # Wrap each line in s/ and /g有这样的输出:
$ sed '1d;s/,/|/;s|,|/|;s|.*|s/&/g|' terms_list.csv
s/Mangifera indica|M. indica/Mangiferaindica/g
s/Oryza sativa|O. sativa/Oryzasativa/g现在,我们使用这个输出在我们想要更改的所有文件上运行sed -i (需要本地编辑GNU ):
sed '1d;s/,/|/;s|,|/|;s|.*|s/&/g|' terms_list.csv | sed -i -Ef- text_data_file_*.csv-E启用扩展正则表达式,因此我们可以使用|进行替换。-f-从标准输入读取sed命令。可以使第一个命令在明确的单词边界方面更加健壮,以避免子字符串匹配:
$ sed '1d;s/,/|/;s|,|)\\b/|;s|.*|s/\\b(&/g|' terms_list.csv
s/\b(Mangifera indica|M. indica)\b/Mangiferaindica/g
s/\b(Oryza sativa|O. sativa)\b/Oryzasativa/g其中\b表示单词边界(也是GNU扩展)。
如果输入包含任何regex元字符,则必须将它们全部转义,因此第一个命令将变成如下所示:
sed '1d;s/[][*+{}()/\|&^$.?]/\\&/g;s/,/|/;s|,|)\\b/|;s|.*|s/\\b(&/g|' terms_list.csv这里的重要补充是第一个通过反斜杠转义元字符来处理元字符的内容:
s/[][*+{}()/\|&^$.?]/\\&/g因此,在最坏的情况下,terms_list.csv包含以下内容
a[abc]*x+\1{2}|-(o).^$?/\a,other,abc&\1def生成的命令如下所示
s/\b(a\[abc\]\*x\+\\1\{2\}\|-\(o\)\.\^\$\?\/\\a|other)\b/abc\&\\1def/g有一种情况还没有涉及到:如果输入的CSV文件在其中一个字段中包含逗号,则必须使用CSV解析器进行预处理。在第三个解决方案中转义regex元字符时忽略逗号。
发布于 2019-05-15 19:41:59
以下是一种完整的方法(即不需要使用shell循环或任何其他方法),使用GNU awk进行"inplace“编辑:
awk -i inplace -F, '
NR==FNR { if (NR>1) { map[$1]=$3; map[$2]=$3 } print; next }
{
for (term in map) {
gsub(term,map[term])
}
print
}
' terms_list.csv text_data_file_*.csv按摩适合。例如,如果您的terms_list文件可能包含RE元文件,那么您应该考虑是否想要在regexp中使用它们,就像您在sed中所做的那样,我们在上面使用gsub(),或者使用一个字符串操作,比如with index()和substr(),而不是gsub(),还应该考虑如何处理部分匹配和/或第一个替换创建以前不存在的项的情况,然后再进行第二个替换等等。
我怀疑这样的东西(未经测试)会满足您的需要(它肯定比您正在运行的sed脚本健壮得多,几乎比shell循环+ sed方法快一个数量级):
awk -i inplace -F, '
NR==FNR {
orig = $0
if (NR > 1) {
gsub(/[^^]/,"[&]",$1)
gsub(/\^/,"\\^",$1)
gsub(/[^^]/,"[&]",$2)
gsub(/\^/,"\\^",$2)
gsub(/&/,"\\&",$3)
map["\\<"$1"\\>"] = $3
map["\\<"$2"\\>"] = $3
}
print orig
next
}
{
for (term in map) {
gsub(term,map[term])
}
print
}
' terms_list.csv text_data_file_*.csvgsubs正在转义任何元字符,因此原始文本和替换文本中的每个字符都会被按字面处理,但是我们将在所有原始术语的周围添加单词边界,以避免部分匹配。
发布于 2019-05-24 14:01:46
2019年-05-24年度最新情况:
经过一些尝试和错误之后,我喜欢BenjaminW的方法。但是,我也对perl进行了调整,发现在这种情况下,perl (v5.22.1)的性能优于sed (GNU 4.2.2)。(使用非常类似的代码--模式结束时需要的代码),perl的速度大约是sed的三倍。请查看时间命令输出。我在下面发布了sed和perl代码,这些代码可以满足我当前的需要。
#!/bin/bash
##################################################
##terms_list.csv content: About 20k lines
#TermFull,TermAbbreviated,TermJoined
#Mangifera indica,M. indica,Mangiferaindica
#Oryza sativa,O. sativa,Oryzasativa
## SCRIPT 1: sed
substitution_with_sed(){
#First generate the substitution pattern script
sed '1d;s/[][*+{}()/\|&^$.?]/\\&/g;s/,/|/;s|,|)\\b/|;s|.*|s/\\b(&/g|' terms_list.csv > terms_list.sed
#1d # Skip the first line; which is the CSV header terms
#s/,/|/ # Replace the first comma with a pipe
#s|,|/| # Replace the second comma with a slash
#s|.*|s/&/g| # Wrap each line in s/ and /g
#s/[][*+{}()/\|&^$.?]/\\&/g #Escape any regex metacharacters with a backslash
##'terms_list.sed' file content
#s/\b(Mangifera indica|M. indica)\b/Mangiferaindica/g
#s/\b(Oryza sativa|O. sativa)\b/Oryzasativa/g
for DATA_FILE in ./DIR/DATA_CSV_FILE*.csv; # About 100k DATA_CSV_FILE*.csv files
do
FILE="$(basename $DATA_FILE)"
echo "Running SED on $DATA_FILE and $FILE"
echo "sed -E -f terms_list.sed < $DATA_FILE > sed-out-$FILE"
time sed -E -f terms_list.sed < $DATA_FILE > sed-out-$FILE
#-E enables extended regular expressions so we can use | for alternation
#-f- reads the sed commands from standard input
# # real 25m55.369s
# # user 25m54.976s
# # sys 0m0.336s
done
}
## SCRIPT 2: perl
substitution_with_perl(){
#First generate the substitution script
sed '1d;s/[][*+{}()/\|&^$.?]/\\&/g;s/,/|/;s|,|)\\b/|;s|.*|s/\\b(&/g;|' terms_list.csv > terms_list.perl
##'terms_list.perl' file content
#s/\b(Mangifera indica|M. indica)\b/Mangiferaindica/g;
#s/\b(Oryza sativa|O. sativa)\b/Oryzasativa/g;
for DATA_FILE in ./DIR/DATA_CSV_FILE*.csv;
do
FILE="$(basename $DATA_FILE)"
echo "Running PERL on $DATA_FILE and $FILE"
echo "perl -p terms_list.perl < $DATA_FILE > perl-out-$FILE"
time perl -p terms_list.perl < $DATA_FILE > perl-out-$FILE
## Read substitution pattern command from file with -p flag
# # real 0m8.120s
# # user 0m8.072s
# # sys 0m0.044s
done
}
#####################################################
##Call functions
substitution_with_sed
substitution_with_perl
#Raw data
#ID000001,Mangifera indica, commonly known as mango, is a species of flowering plant in the sumac and poison ivy family Anacardiaceae. M. indica is a popular fruit in India.
#ID000002,Oryza sativa, commonly known as Asian rice, is the plant species most commonly referred to in English as rice. O. sativa contains two major subspecies: the sticky, short-grained #japonica or sinica variety, and the nonsticky, long-grained indica rice variety.
#Desired processed output data in 'sed-out-$FILE'/'perl-out-$FILE' file content
#ID000001,Mangiferaindica, commonly known as mango, is a species of flowering plant in the sumac and poison ivy family Anacardiaceae. Mangiferaindica is a popular fruit in India.
#ID000002,Oryzasativa, commonly known as Asian rice, is the plant species most commonly referred to in English as rice. Oryzasativa contains two major subspecies: the sticky, short-grained japonica or sinica variety, and the nonsticky, long-grained indica rice variety.@EdMorton,@CharlesDuffy,@BenjaminW,再次感谢您的评论和解决方案。你提供的信息对我非常有用,在过去的一周里我学到了很多。我已经采纳了你的建议,并在下面为像我这样天真的程序员做了总结/文件。
我对python解决方案也很好奇,当我有了工作代码和一些基准时,我会进行更新。
https://stackoverflow.com/questions/56156505
复制相似问题