查找部分重复的行,保留一审并保留其余部分

时间:2019-06-19 10:33:13

标签: regex powershell notepad++

我已经在Notepad ++中用RegEx尝试了几件事,但是我什至不确定是否可行。我也使用PowerShell尝试了一两件事,但没有任何效果。

数据如下:

007.130.0001;E2
007.130.0001;E4
007.130.0001;M4 20.1
007.130.0001;M4 20.1 NX
007.130.0002;E2
007.130.0002;E4
007.130.0002;M2_duplicate
007.130.0002;M4 20.1
007.130.0002;M4 20.1 NX
007.130.0008;M4 20.1 NX
007.130.0008;M4 20.3_M4 25.3
007.130.0008;M4 20.3_M4 25.3 NX
011.130.0124;E-Serie_duplicate
011.130.0124;M4 20.1
011.130.0124;M4 20.1 NX

我想要这样(选项A):

007.130.0001;E2
;E4
;M4 20.1
;M4 20.1 NX
007.130.0002;E2
;E4
;M2_duplicate
;M4 20.1
;M4 20.1 NX
007.130.0008;M4 20.1 NX
;M4 20.3_M4 25.3
;M4 20.3_M4 25.3 NX
011.130.0124;E-Serie_duplicate
;M4 20.1
;M4 20.1 NX

或那个(选项B):

007.130.0001;E2;E4;M4 20.1;M4 20.1 NX
007.130.0002;E2;E4;M2_duplicate;M4 20.1;M4 20.1 NX
007.130.0008;M4 20.1 NX;M4 20.3_M4 25.3;M4 20.3_M4 25.3 NX
011.130.0124;E-Serie_duplicate;M4 20.1;M4 20.1 NX

因此,基本上,我想以特殊字符(;)分割一行,并检查第一部分是否与下一行重复,删除除第一部分外的所有重复内容,并保持其余部分不变。

我最接近的是这个RegEx:

Find: ^([^;]+;).+\R(.*?\1.+(?:\R|$))+
Replace: \2

但是,我将得出以下结论:

007.130.0001;M4 20.1 NX
007.130.0002;M4 20.1 NX
007.130.0008;M4 20.3_M4 25.3 NX
011.130.0124;M4 20.1 NX

4 个答案:

答案 0 :(得分:1)

以下powershell命令序列可以解决问题:

$repeats = [Linq.Enumerable]::Count([System.IO.File]::ReadLines("<path to current dir>\\data.txt")) - 1; copy-item -path data.txt -destination work.txt; for ($i=1; $i -le $repeats; $i++) { (Get-Content -Raw work.txt) -replace '(?s)(\d{3}\.\d{3}\.\d{4};)(([^\r\n]+[\r\n]+)*)\1', '$1$2' | Out-File result.txt; move-item -path result.txt -destination work.txt -force }; move-item -path work.txt -destination result.txt -force

说明

脚本

为便于讨论,命令行每行分成一个命令。假定原始数据在“ data.txt and a temp file work.txt can be used. result.txt`中将包含结果。

基本思路:

  • 使用向后引用设计正则表达式来表达重复出现的匹配项。
  • 重复执行此正则表达式。
    每次运行都会为第一列中的每个值删除1个重复项。
  • 保守地预先估计最大重复次数。

该解决方案远非优雅高效(请参阅评论部分以获取一些想法)。

  1. 估计运行次数。  我们将看到,每次运行都会为第一列中的每个值删除1个重复项。因此,在最坏的情况下(即,每行以相同的前缀开头),这意味着no. of lines - 1运行。确定该数字,然后将其存储在变量$repeats中。
     信用:该行为taken from another SO answer

    $repeats = [Linq.Enumerable]::Count([System.IO.File]::ReadLines("<path to current dir>\\data.txt")) - 1;
    
  2. 文书工作:将原件复制到工作文件

    copy-item -path data.txt -destination work.txt;
    
  3. 重复更换$repeats

    for ($i=1; $i -le $repeats; $i++) {
    
  4. 基于正则表达式的替换。
        -匹配行前缀+该行的其余部分+任意数量的没有前缀的行+匹配的前缀再次出现。
        -文书工作:将结果文件重命名为工作文件

    贷方:用于将正则表达式应用于从this SO answer提取的文本文件的命令

        (Get-Content -Raw work.txt) -replace '(?s)(\d{3}\.\d{3}\.\d{4};)(([^\r\n]+[\r\n]+)*)\1', '$1$2' | Out-File result.txt;
        move-item -path result.txt -destination work.txt -force 
    };
    
  5. 文书工作:将工作文件的最后一个实例移到结果文件中

    move-item -path work.txt -destination result.txt -force
    

正则表达式

powershell的regex方言是.NET。

面临的挑战是在保留中间材料的同时删除每个前缀副本。正则表达式的一次执行不会成功,因为连续的匹配会重叠。

分步讨论:

a。选择单行匹配。       因为比赛必须越过线边界

(?s)

b。前缀匹配模式       显然,该子模式需要根据实际的前缀格式进行更改。此格式(用.分隔的3-3-4个十进制数字vlock)是从示例中得出的。
      注意尾随;和括号来定义此子模式的匹配捕获组。以后会引用此捕获组/匹配

(\d{3}\.\d{3}\.\d{4};)

c。中间文字
      b.的子表达式匹配的行的其余部分+行分隔符序列+任意数量的行。

  Due to the greedy greedy ( 'match as much as you can' ) nature of repetition operators ( `*` ), this part would match the remainder of the file (assuming it ends with a line separator).

(([^\r\n]+[\r\n]+)*)
d。前缀克隆      必须再次出现与b.中的子表达式匹配的前缀,才能进行替换。实际上,这与b.

匹配的前缀的最后一个克隆匹配
\1

按照设计,正则表达式仅在行的开头检测克隆

评论

虽然有可能以与给定模式相似的方式匹配整个前缀克隆集及其中间字符串-基本上选择非贪婪(“尽可能少地匹配”)匹配-我不了解在指定替换项时精确删除前缀克隆的任何方法。

可以通过仅匹配具有相同前缀的连续行来减少重复次数,从而消除每次匹配中的第二次出现。因此,每遍将有多个比赛/替换。基本上,这减少了迭代次数log ( no. of lines )。它要求修改后的正则表达式在2个连续的前缀出现之间适应1条中间线。此修改仅适用于非常大的文件

原始文件的表格形式表明数据来自数据库或电子表格。这些工作环境将更适合完成手头的任务,因此,如果有机会在将数据转储为文件之前对其进行修改,那将是首选方法。

可以通过适当的powershell命令或命令行工具的形式来使用更合适的工具,以在第一列中进行某种形式的列解析和重复数据删除。

答案 1 :(得分:0)

并不是很聪明的解决方案,但是可以。

您必须多次击打全部替换才能完成任务。

  • Ctrl + H
  • 查找内容:^([^;]+;)(.+)\R(?:\1|((?=[^;]+;)))
  • 替换为:$1$2(?3\n$3:;)
  • 检查环绕
  • 检查正则表达式
  • 取消检查. matches newline
  • 全部替换

说明:

^                   # beginning of line
  ([^;]+;)          # group 1, 1 or more non semi-colon then a semi-colon
  (.+)              # group 2, 1 or more any character but newline
  \R                # any kind of linebreak
  (?:               # start non capture group
    \1              # same as group 1
   |                # OR
    (               # start group 3
      (?=[^;]+;)    # positive lookahead, make sure whave after: 1 or more non semi-colon then a semi-colon
    )               # end group 3
  )                 # end group

替换:

$1              # content of group 1
$2              # content of group 2
(?3             # if group 3 exists
  \n$3          # linefeed then content of group 3  (you can use \r\n if you want)
 :              # else
  ;             # semicolon
)               # end conditional

给定示例的结果

007.130.0001;E2;E4;M4 20.1;M4 20.1 NX
007.130.0002;E2;E4;M2_duplicate;M4 20.1;M4 20.1 NX
007.130.0008;M4 20.1 NX;M4 20.3_M4 25.3;M4 20.3_M4 25.3 NX
011.130.0124;E-Serie_duplicate;M4 20.1;M4 20.1 NX

屏幕截图:

enter image description here

答案 2 :(得分:0)

如果有可用的xslt处理器,这可能是一种可行的方法:

  • 将类似于csv的输入文件转换为简单的xml文件
  • 将xslt样式表应用于:

    • 按第一列的内容对数据进行分组
    • 在第一列中删除重复项
    • 以文本格式写入结果

使用的xsl样式表(命令中的gcsv.xslt)是:

<?xml version="1.0" encoding="UTF-8"?>
<!--
    SO
    https://stackoverflow.com/questions/56665631/find-partially-duplicated-lines-keep-first-instance-and-leave-the-rest-untouche/56667131#56667131

    19.06.2019 14:57:14
-->
<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:arc="http://xml.solusy.eu/oryco/mail/archive/190214"
    exclude-result-prefixes="#all"
    version="3.0"
>
    <!-- textual output and handy variables -->
    <xsl:output method="text"/>
    <xsl:variable name="delimiter" select="';'"/>
    <xsl:variable name="newline"   select="'&#x0a;'"/>

    <!-- group rows by the first column's content --> 
    <xsl:template match="/">
        <xsl:for-each-group
            select="/file/r"
            group-by="./c[1]/text()"
        >
                <xsl:apply-templates select="current-group()[position() = 1]/c"/>
                <xsl:apply-templates select="current-group()[position() > 1]"/>
        </xsl:for-each-group>
    </xsl:template>

    <!-- Deduplicate the first column in all but the first row of a group -->
    <xsl:template match="r">
        <xsl:apply-templates select="./c[position() > 1]"/>
    </xsl:template>

    <!-- Write out column content as plain text -->
    <xsl:template match="c">
        <xsl:value-of select="."/>
        <xsl:choose>
            <xsl:when test="position() = last()">
                <xsl:value-of select="$newline"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$delimiter"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="child::node() | @*"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

整个过程可由powershell命令序列驱动,如下所示:

(Get-Content -Raw data.txt) -replace ';', '</c><c>' -replace '(?s)[\r\n]+$', '' -replace '(?m)^', '<r><c>' -replace '(?m)$', '</c></r>' -replace '(?s)^', "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`n<file>" -replace '(?s)$', '</file>' | Out-File -Encoding UTF8 work.xml; java -jar "<path_to_saxon>" -s:"<path_to_work_dir>\work.xml" -xsl:"<path_to_work_dir>\gcsv.xslt" -o:"<path_to_work_dir>\result.txt"

分步说明:

  1. 将原始文件转换为xml。
      如果没有转义字符,这对于类似csv的内容很容易:      -csv字段未定界
         -字段内容中不出现字段分隔符char(;
         -所有文件字符都可以按原样在xml

    中使用

    文件的每一行都转换为<r>元素,一行中的每个字段都将转换为<c>元素,其中字段数据为文本内容。完整的文件被包装到单个根元素(<file>)中,并且为了使挑剔的xslt处理器满意,添加了一个标准的xml序言。

    可以通过一系列基于正则表达式的替换操作来实现这些任务,这些替换操作将;转换为</c><c>并在每个起始和结尾处分别插入<r><c></c></r>在多行模式下分别排成一行(检查自己的语法是否为有效的xml)。

    (Get-Content -Raw data.txt) -replace ';', '</c><c>' -replace '(?s)[\r\n]+$', '' -replace '(?m)^', '<r><c>' -replace '(?m)$', '</c></r>' -replace '(?s)^', "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`n<file>" -replace '(?s)$', '</file>' | Out-File -Encoding UTF8 work.xml;
    
    1. 使用xslt处理器处理输入文件。
      示例命令使用免费的Saxon(Saxon HE;请检查their homepage以获取许可证详细信息)。任何其他xslt2处理器都应该可以。

         java -jar "<path_to_saxon>" -s:"<path_to_work_dir>\work.xml" -xsl:"<path_to_work_dir>\gcsv.xslt" -o:"<path_to_work_dir>\result.txt"
      

答案 3 :(得分:0)

这是完成任务的简单Perl脚本:

在输入文件所在的目录中运行此命令

Run Tests After the Build

说明:

perl -nE 'chomp;($k,$v)=split(/;/,$_,2);$h{$k}.=";$v";}{say $_.$h{$_} for sort keys%h' file > output

cat output
007.130.0001;E2;E4;M4 20.1;M4 20.1 NX
007.130.0002;E2;E4;M2_duplicate;M4 20.1;M4 20.1 NX
007.130.0008;M4 20.1 NX;M4 20.3_M4 25.3;M4 20.3_M4 25.3 NX
011.130.0124;E-Serie_duplicate;M4 20.1;M4 20.1 NX