Bash 删除具有相似前缀的重复行

Bash 删除具有相似前缀的重复行,bash,awk,sed,duplicates,Bash,Awk,Sed,Duplicates,我需要删除具有重复前缀的文件中的类似行,并保留唯一的行 由此, abc/def/ghi/ abc/def/ghi/jkl/one/ abc/def/ghi/jkl/two/ 123/456/ 123/456/789/ xyz/ 对此 abc/def/ghi/jkl/one/ abc/def/ghi/jkl/two/ 123/456/789/ xyz/ 感谢您的建议,一种快速而肮脏的方法是: $ while read elem; do echo -n "$elem " ; grep $elem

我需要删除具有重复前缀的文件中的类似行,并保留唯一的行

由此,

abc/def/ghi/
abc/def/ghi/jkl/one/
abc/def/ghi/jkl/two/
123/456/
123/456/789/
xyz/
对此

abc/def/ghi/jkl/one/
abc/def/ghi/jkl/two/
123/456/789/
xyz/

感谢您的建议,

一种快速而肮脏的方法是:

$ while read elem; do echo -n "$elem " ; grep $elem file| wc -l; done <file | awk '$2==1{print $1}'
abc/def/ghi/jkl/one/
abc/def/ghi/jkl/two/
123/456/789/
xyz/

$whilereadelem;do echo-n“$elem”;grep$elem文件| wc-l;done以下awk执行所请求的操作,它读取文件两次

  • 在第一个过程中,它建立每行所有可能的前缀
  • 第二步,它检查行是否是可能的前缀,如果不是打印
代码是:

awk -F'/' '(NR==FNR){s="";for(i=1;i<=NF-2;i++){s=s$i"/";a[s]};next}
           {if (! ($0 in a) ) {print $0}}' <file> <file>

考虑到此构造读取文件(N+1)次,其中N是行数。

步骤1:此解决方案基于允许对输出重新排序的假设。如果是这样,那么在处理之前对输入文件进行反向排序应该会更快。通过反向排序,我们只需要比较每个循环中的两个连续行,而不需要搜索所有文件或所有“已知前缀”。我理解一行定义为前缀,如果它是任何其他行的前缀,则应将其删除。下面是一个删除文件中前缀的示例,允许重新排序

#!/bin/bash

f=sample.txt                                 # sample data

p=''                                         # previous line = empty

sort -r "$f" | \
  while IFS= read -r s || [[ -n "$s" ]]; do  # reverse sort, then read string (line)
    [[ "$s" = "${p:0:${#s}}" ]] || \
      printf "%s\n" "$s"                     # if s is not prefix of p, then print it
    p="$s"
  done
#!/bin/bash

f=sample.txt
p=''

cat -n "$f" | \
  sed 's:\t:|:' | \
  sort -r -t'|' -k2 | \
  while IFS='|' read -r i s || [[ -n "$s" ]]; do
    [[ "$s" = "${p:0:${#s}}" ]] || printf "%s|%s\n" "$i" "$s"
    p="$s"
  done | \
  sort -n -t'|' -k1 | \
  sed 's:^.*|::'
解释:
${p:0:${s}
在字符串
p
中取第一个
${s}
(一个
s
)字符

测试:

第2步:如果您确实需要保留顺序,则此脚本是删除所有前缀的示例,不允许重新排序

#!/bin/bash

f=sample.txt                                 # sample data

p=''                                         # previous line = empty

sort -r "$f" | \
  while IFS= read -r s || [[ -n "$s" ]]; do  # reverse sort, then read string (line)
    [[ "$s" = "${p:0:${#s}}" ]] || \
      printf "%s\n" "$s"                     # if s is not prefix of p, then print it
    p="$s"
  done
#!/bin/bash

f=sample.txt
p=''

cat -n "$f" | \
  sed 's:\t:|:' | \
  sort -r -t'|' -k2 | \
  while IFS='|' read -r i s || [[ -n "$s" ]]; do
    [[ "$s" = "${p:0:${#s}}" ]] || printf "%s|%s\n" "$i" "$s"
    p="$s"
  done | \
  sort -n -t'|' -k1 | \
  sed 's:^.*|::'
说明:

  • cat-n
    :为所有行编号
  • sed's:\t:|:'
    :使用“|”作为分隔符——如果需要,您需要将其更改为另一个分隔符
  • sort-r-t'|'-k2
    :使用分隔符='|'反向排序并使用键2
  • 当。。。完成
    :类似于步骤1的解决方案
  • 排序-n-t'|'-k1
    :按原始顺序排序(编号排序)
  • sed的:^..*.::'
    :删除编号
  • 测试:

    注意:在这两种解决方案中,成本最高的操作是调用
    排序
    。步骤1中的解决方案调用
    sort
    一次,步骤2中的解决方案调用
    sort
    两次。所有其他操作(
    cat
    sed
    while
    、字符串比较等)的成本水平不相同


    在步骤2的解决方案中,
    cat+sed+while+sed
    相当于扫描该文件4次(理论上,由于管道原因,可以并行执行)。

    如果允许对输出进行重新排序,则回答此问题

    sort -r file | awk 'a!~"^"$0{a=$0;print}'
    
  • sort-r文件
    :以相反的方式对行进行排序,这样具有相同图案的较长行将被放置在具有相同图案的较短行之前

  • awk'a~“^”$0{a=$0;print}'
    :分析排序输出,其中
    a
    保存前一行,
    $0
    保存当前行

    • a~“^”$0
      检查当前行是否不是前一行开头的子字符串
    • 如果
      $0
      不是子字符串(即不相似的前缀),我们将
      打印它并将新字符串保存在
      a
      (与下一行进行比较)

  • 第一行
    $0
    不在
    a
    中,因为
    a
    没有赋值(第一行总是打印出来)

    您尝试了什么?将您的研究成果发布到问题中,即使它们微不足道,如果您展示您迄今为止尝试过的代码并描述您在其中遇到的问题,您将得到更友好的接待和更好的帮助。如果没有代码,你的问题看起来像是一个免费咨询的请求,很多人不喜欢。除此之外。。。如何定义前缀?从你所写的,第1,2和3行都有相同的前缀,但你的例子说没有。我的回答是否给了你一些启示?它是否如预期的那样工作?抱歉,我将在下次发布。我试过到处玩,但没办法弄清楚如何去除这些复制品。艾伦的回答解决了问题,克万托尔的回答也解决了问题。干杯,很好的解决方案。如果
    a=“/abc/xyz”
    $0=“/xyz”
    ,这也有效吗?谢谢:)。要同时删除“post_fix”,您可以使用
    rev
    命令反转所有字符,比较并
    rev
    back:)<代码>修订文件|排序-r | awk'a~$0{print;a=$0}'| rev
    No,我的意思是您也将匹配不在开头的字符串。如果您希望
    $0
    不在
    a
    的开头,它应该是
    a~“^”$0
    $ ./remove-prefix.sh 
    abc/def/ghi/jkl/one/one
    abc/def/ghi/jkl/two/two
    123/456/789/
    xyz/
    
    sort -r file | awk 'a!~"^"$0{a=$0;print}'