【问题标题】:What is an efficient way to replace list of strings with another list in Unix file?用 Unix 文件中的另一个列表替换字符串列表的有效方法是什么?
【发布时间】:2011-11-04 02:45:08
【问题描述】:

假设我有两个字符串列表(列表 A 和列表 B),每个列表中的条目数 N 完全相同,我想用 A 的第 n 个元素替换所有出现的 A 的第 n 个元素B 在 Unix 中的文件中(最好使用 Bash 脚本)。

最有效的方法是什么?

一种低效的方法是对“sed s/stringA/stringB/g”进行 N 次调用。

【问题讨论】:

    标签: bash unix scripting file-io


    【解决方案1】:

    这将一次性完成。它将 listA 和 listB 读入 awk 数组,然后对于 linput 的每一行,它检查每个单词,如果在 listA 中找到该单词,则将该单词替换为 listB 中的相应单词。

    awk '
        FILENAME == ARGV[1] { listA[$1] = FNR; next }
        FILENAME == ARGV[2] { listB[FNR] = $1; next }
        {
            for (i = 1; i <= NF; i++) {
                if ($i in listA) {
                    $i = listB[listA[$i]]
                }
            }
            print
        }
    ' listA listB filename > filename.new
    mv filename.new filename
    

    我假设 listA 中的字符串不包含空格(awk 的默认字段分隔符)

    【讨论】:

    • 总体不错,但存在潜在问题。此解决方案不一定保留进行更改的行上的字间距;空格的运行变为单个空格。由于我们不知道文本的性质,它可能不是问题,甚至可能成为优势。无论如何,我要 +1。
    • 这显然比下面编写 sed 脚本的解决方案更有效。使用 sed 解决方案需要 3 天的时间在 3 分钟内完成。也只替换整个单词,即使这不是所要求的。
    • 这解决了目的,但是如何保留空白?看来 awk 脚本只用一个空格替换了它们。
    • 您将使用substr 函数查找要替换的单词前后的行部分,并将这些部分与替换连接起来。或者用 perl 重写,这样更容易维护分隔符
    【解决方案2】:

    调用编写 sed 脚本的sed,然后再调用一次来使用它?如果您的列表在文件listAlistB 中,那么:

    paste -d : listA listB | sed 's/\([^:]*\):\([^:]*\)/s%\1%\2%/' > sed.script
    sed -f sed.script files.to.be.mapped.*
    

    我正在对不包含冒号或百分号的“单词”做出一些全面的假设,但您可以适应这一点。某些版本的sed 对可以指定的命令数量有上限;如果这是一个问题,因为您的单词列表足够大,那么您可能必须将生成的 sed 脚本拆分为单独的文件,这些文件将被应用 - 或者更改为使用没有限制的东西(例如 Perl)。

    另一个需要注意的是变化的顺序。如果要交换两个单词,则需要仔细制作单词列表。一般来说,如果您将 (1) wordA 映射到 wordB 以及 (2) wordB 到 wordC,那么 sed 脚本在映射 (2) 之前还是之后进行映射 (1) 很重要。

    显示的脚本不注意单词边界;您可以通过多种方式对它们进行谨慎处理,具体取决于您使用的sed 版本以及您对单词构成的标准。

    【讨论】:

    • B 中的一个单词完全或部分在 A 中也存在潜在问题。真正的解决方案可能需要将输入分解为单词并更改一次(如果有的话)。
    • @lhf:我认为你提到的问题已经包含在我关于词边界的讨论中。无论如何,正确的行为取决于问题的定义。最多更改一次单词是一个合理的定义,但不是唯一的定义。您甚至可以使用sed 进行编码,在每次替换操作后使用“t”命令。
    • 如果替换字符串中有 '\',它们会被删除,因此需要在脚本中用 '\\' 替换,否则这是一个很棒的解决方案!
    【解决方案3】:

    我需要做一些类似的事情,最后我根据地图文件生成了 sed 命令:

    $ cat file.map
    abc => 123
    def => 456
    ghi => 789
    
    $ cat stuff.txt
    abc jdy kdt
    kdb def gbk
    qng pbf ghi
    non non non
    try one abc
    
    $ sed `cat file.map | awk '{print "-e s/"$1"/"$3"/"}'`<<<"`cat stuff.txt`"
    123 jdy kdt
    kdb 456 gbk
    qng pbf 789
    non non non
    try one 123
    

    确保您的 shell 支持与地图中一样多的 sed 参数。

    【讨论】:

    • 漂亮的单线!!
    • sed & bash 版本:sed -f &lt;(sed 's/=&gt; //;s# #/#;s#$#/#;s#^#s/#' file.map) stuff.txt
    【解决方案4】:

    这对于 Tcl 来说相当简单:

    set fA [open listA r]
    set fB [open listB r]
    set fin [open input.file r]
    set fout [open output.file w]
    
    # read listA and listB and create the mapping of corresponding lines
    while {[gets $fA strA] != -1} {
        set strB [gets $fB]
        lappend map $strA $strB
    }
    
    # apply the mapping to the input file
    puts $fout [string map $map [read $fin]]
    
    # if the file is large, do it line by line instead
    #while {[gets $fin line] != -1} {
    #    puts $fout [string map $map $line]
    #}
    
    close $fA
    close $fB
    close $fin
    close $fout
    
    file rename output.file input.file
    

    【讨论】:

      【解决方案5】:

      您可以在bash 中执行此操作。将您的列表放入数组中。

      listA=(a b c)
      listB=(d e f)
      data=$(<file)
      echo "${data//${listA[2]}/${listB[2]}}" #change the 3rd element. Redirect to file where necessary
      

      【讨论】:

        【解决方案6】:

        使用 tr(1)(翻译或删除字符):

         cat file | tr 'abc' 'XYZ' > file_new
         mv file_new file
        

        【讨论】:

        • 他想替换整个字符串而不是单个字符
        猜你喜欢
        • 1970-01-01
        • 2015-01-11
        • 1970-01-01
        • 2011-11-28
        • 2021-01-09
        • 1970-01-01
        • 2021-04-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多