【问题标题】:Vim Regex Duplicate Lines GroupingVim 正则表达式重复行分组
【发布时间】:2011-11-27 21:59:20
【问题描述】:

我有一个这样的日志文件:

12 adsflljl
12 hgfahld
12 ash;al
13 a;jfda
13 asldfj
15 ;aljdf
16 a;dlfj
19 adads
19 adfasf
20 aaaadsf

我想将它们“分组”为以下两者之一:

12 adsfllj, 12 hgfahld, 12 ash;al
13 a;jfda, 13 asldfj
15 ;aljdf
16 a;dlfj
19 adads, 19 adfasf
20 aaaadsf

或者

12 adsfllj, hgfahld, ash;al
13 a;jfda, asldfj
15 ;aljdf
16 a;dlfj
19 adads, adfasf
20 aaaadsf

我完全被困住了。如果 vim 不这样做,我也有 sed 和 awk 和 bash。我只是不想写一个bash脚本,我想增加我的regex-fu

【问题讨论】:

    标签: regex sorting vim sed awk


    【解决方案1】:

    我只会使用 awk:

    awk '
      {
        sep = val[$1] ? ", " : ""
        val[$1] = val[$1] sep $2
      }
      END {for (v in val) print v, val[v]}
    ' log.file | sort > new.file
    

    【讨论】:

    • 我不知道这是如何工作的,但确实如此,所以这是我打开一个关于 awk 的教程来了解原因的一个很好的理由。太棒了!我的fu已经准备好增加了!
    • awk 的一个奇怪特性是它如何进行字符串连接:没有连接运算符(如+.)——并排放置两个变量,值将是串联。运算符(如上面的 ?:)通常像 C 运算符一样工作。此外,未设置变量的行为类似于空字符串或数字为零,因此为 false。
    【解决方案2】:

    在 Vim 中你可以使用:

    :%s/\(\(\d\+\) .*\)\n\2/\1, \2/g 
    

    这意味着:如果一组数字在换行后匹配,则删除换行并放置一个逗号。如果您不熟悉它们,\1\2 是反向引用。

    不幸的是,这一次只会合并两次,因此您必须多次运行它才能实现目标。

    编辑:一次完成的一种方法是循环并利用这样一个事实,即一旦文件不再匹配,就会发出错误。虽然这个错误有点烦人,但我不能用单线做得更好:

    :while 1 | :%s/\(\(\d\+\) .*\)\n\2/\1, \2/g | :endwhile
    

    【讨论】:

    • 谢谢,这也太好了,我给你点赞,他只是第一个......
    【解决方案3】:

    我认为在这里使用正则表达式不是一个好主意。您可以在@glenn jackman 用 vimscript 编写的解决方案中找到相同的想法:

    function JoinLog()
        let d={}
        g/\v^\S+\s/let [ds, k, t; dl]=matchlist(getline('.'), '\v^(\S+)\s+(.*)') |
                  \let d[k]=get(d, k, [])+[t]
        %delete _
        call setline(1, map(sort(keys(d)), 'v:val." ".join(d[v:val], ", ")'))
    endfunction
    

    你可以保持顺序而不是排序:

    function JoinLog()
        let d={}
        let ordered=[]
        g/\v^\S+\s/let [ds, k, t; dl]=matchlist(getline('.'), '\v^(\S+)\s+(.*)') |
                  \if has_key(d, k) | let d[k]+=[t] |
                  \else             | let ordered+=[k] | let d[k]=[t] |
                  \endif
        %delete _
        call setline(1, map(copy(ordered), 'v:val." ".join(d[v:val], ", ")'))
    endfunction
    

    【讨论】:

      【解决方案4】:

      在 Vim 中,我会使用命令

      :g/^\d\+/y|if+@"==getline(line('.')-1)|s//,/|-j!
      

      如果保证第一列总是包含数字ID。

      否则,我将修改 if 条件如下。

      :g/^\S\+/y|if matchstr(@",@/)==matchstr(getline(line('.')-1),@/)|s//,/|-j!
      

      【讨论】:

      • @Peter:它的性能也很好:这两个命令都比 UncleZeiv 的替换循环或 Benoit 的命令序列快得多,并且与 ZyX 的脚本速度(甚至快一点)。
      【解决方案5】:

      另一种方法,这次使用宏(我建议您使用另一种解决方案,这个只是表明有很多方法可以做到):

      gg:%s/$/,enterqa0V?ctrl-rctrl-w\>\&^enter@987654324 @返回

      解释:

      • gg => 转到文件开头
      • :%s/$/, => 将逗号附加到每一行
      • qa => 开始将宏记录到寄存器a
      • 0V => 转到第一列,开始逐行选择
      • ? => 向后查找(你必须有set wrapscan
        • ctrl-r ctrl-w 在光标下插入单词。
        • \> 确保词尾
        • \&^ 确保在行首的模式匹配。您不能将^ 放在模式的开头,因为如果设置了incsearch,那么一旦您输入^ 然后ctrl-r ctrl-w kbd> 将打印光标下的单词,该单词将移至上一行。
      • J 将用空格连接视觉选择中的所有行。
      • j 将转到下一行
      • q 将停止录制宏
      • 100@a 将播放宏 100 次。
      • :%s/.$// 将删除结尾的逗号。

      【讨论】:

        猜你喜欢
        • 2019-01-25
        • 2023-03-15
        • 2010-12-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-10-30
        • 1970-01-01
        相关资源
        最近更新 更多