首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何对100个文本文件(每行用50k行)进行“搜索和替换”20k项的最小磁盘I/O操作

如何对100个文本文件(每行用50k行)进行“搜索和替换”20k项的最小磁盘I/O操作
EN

Stack Overflow用户
提问于 2019-05-15 19:37:33
回答 3查看 116关注 0票数 2

我想用非结构化文本对大约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。

代码语言:javascript
复制
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)来完成这项任务,您能给我一些建议/指导吗?

以下是这两个文件的示例数据:

  1. ‘text _ data _file_0001.csv’:100个数据文件之一,‘text_data_file_0001.csv’包含非结构化数据,如下所示,其中文本中包含‘TermFull’和‘TermAbbreviated’。每个文件的大小约为50 MB和50k行。 芒果( ID000001 ),曼陀罗,俗称芒果,是苏木科和毒藤科的一种开花植物。籼稻是印度的一种受欢迎的水果。水稻( ID000002,Oryza ),俗称亚洲水稻,是英语中最常用的水稻种。野生稻含有两个主要亚种:粘性、短粒粳稻或中国粳稻品种和非粘性长粒籼稻品种。
  2. 'terms_list.csv‘文件:搜索词'TermFull’和'TermAbbreviated',替换术语'TermJoined‘存储在’terms_list.csv‘中,包含20k行,如下所示 TermFull,TermAbbreviated,TermJoined Mangifera indica,M. indica,Mangiferaindica Oryza sativa,O.ativa,Oryzasativa
  3. 所需输出文件“text_data_file0001.csv”如下所示,将“TermFull”和“TermAbbreviated”替换为“TermJoined” 芒果( ID000001,Mangiferaindica ),俗称芒果(Mangiferaindica),是苏木科( sumac )有毒常春藤科的一种开花植物。Mangiferaindica是印度流行的水果。水稻( ID000002,Oryzasativa ),俗称亚洲水稻,是英语中最常被称为水稻的植物品种#。稻谷( Oryzasativa )由两大亚种组成:粘性、短粒粳稻或中国粳稻品种和非粘性长粒籼稻品种。
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2019-05-15 22:28:16

您可以使用sed从terms_list.csv构建sed脚本。

代码语言:javascript
复制
sed '1d;s/,/|/;s|,|/|;s|.*|s/&/g|' terms_list.csv

它的工作如下:

代码语言:javascript
复制
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

有这样的输出:

代码语言:javascript
复制
$ 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 ):

代码语言:javascript
复制
sed '1d;s/,/|/;s|,|/|;s|.*|s/&/g|' terms_list.csv | sed -i -Ef- text_data_file_*.csv
  • -E启用扩展正则表达式,因此我们可以使用|进行替换。
  • -f-从标准输入读取sed命令。

可以使第一个命令在明确的单词边界方面更加健壮,以避免子字符串匹配:

代码语言:javascript
复制
$ 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元字符,则必须将它们全部转义,因此第一个命令将变成如下所示:

代码语言:javascript
复制
sed '1d;s/[][*+{}()/\|&^$.?]/\\&/g;s/,/|/;s|,|)\\b/|;s|.*|s/\\b(&/g|' terms_list.csv

这里的重要补充是第一个通过反斜杠转义元字符来处理元字符的内容:

代码语言:javascript
复制
s/[][*+{}()/\|&^$.?]/\\&/g

因此,在最坏的情况下,terms_list.csv包含以下内容

代码语言:javascript
复制
a[abc]*x+\1{2}|-(o).^$?/\a,other,abc&\1def

生成的命令如下所示

代码语言:javascript
复制
s/\b(a\[abc\]\*x\+\\1\{2\}\|-\(o\)\.\^\$\?\/\\a|other)\b/abc\&\\1def/g

有一种情况还没有涉及到:如果输入的CSV文件在其中一个字段中包含逗号,则必须使用CSV解析器进行预处理。在第三个解决方案中转义regex元字符时忽略逗号。

票数 1
EN

Stack Overflow用户

发布于 2019-05-15 19:41:59

以下是一种完整的方法(即不需要使用shell循环或任何其他方法),使用GNU awk进行"inplace“编辑:

代码语言:javascript
复制
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方法快一个数量级):

代码语言:javascript
复制
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_*.csv

gsubs正在转义任何元字符,因此原始文本和替换文本中的每个字符都会被按字面处理,但是我们将在所有原始术语的周围添加单词边界,以避免部分匹配。

票数 4
EN

Stack Overflow用户

发布于 2019-05-24 14:01:46

2019年-05-24年度最新情况:

经过一些尝试和错误之后,我喜欢BenjaminW的方法。但是,我也对perl进行了调整,发现在这种情况下,perl (v5.22.1)的性能优于sed (GNU 4.2.2)。(使用非常类似的代码--模式结束时需要的代码),perl的速度大约是sed的三倍。请查看时间命令输出。我在下面发布了sed和perl代码,这些代码可以满足我当前的需要。

代码语言:javascript
复制
#!/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,再次感谢您的评论和解决方案。你提供的信息对我非常有用,在过去的一周里我学到了很多。我已经采纳了你的建议,并在下面为像我这样天真的程序员做了总结/文件。

  1. 谢谢@EdMorton。注意在替代模式下的元租船!我说过了。在我的数据中,它在RegExp中意味着“一切”。需要用反斜杠来逃脱。
  2. 多亏了@CharlesDuffy;当在shell中使用任何循环迭代许多项或行时,使用该循环中的任何外部工具都会大大降低效率。与上面的代码相比,下面的新代码效率很高。
  3. 感谢@CharlesDuffy;而IFS=,读取-r x y z _;do将读取CSV并赋值变量。
  4. 感谢@CharlesDuffy;echo "File:$DATA_FILE x:$x欧元y:$y溶胶z:$z“;将所有东西作为一个字符串传递给回送比我在上面的初始代码中要好得多。

我对python解决方案也很好奇,当我有了工作代码和一些基准时,我会进行更新。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/56156505

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档