【问题标题】:Delete line from text file with line numbers from another file从另一个文件中删除行号的文本文件中的行
【发布时间】:2012-07-07 08:28:55
【问题描述】:

我有一个文本文件,其中包含一个巨大的行号列表,我必须从另一个主文件中删除它。这是我的数据的样子

lines.txt

1
2
4
5
22
36
400
...

documents.txt

string1
string2
string3
...

如果我有一个简短的行号列表,我可以轻松使用

sed -i '1d,4d,5d' documents.txt

但是有很多行号我必须删除。此外,我可以使用 bash/perl 脚本将行号存储在数组中,并回显不在数组中的行。但我想知道是否有内置命令可以做到这一点。

任何帮助将不胜感激。

【问题讨论】:

  • 只是一个观察,不知道 sed 的工作原理,但是如果文件在内存中被修改并且你删除了第 1 行,你的旧第 4 行现在将是当前第 3 行。
  • 为什么不使用sed 来构建'#d,...' 字符串?
  • @WesMiller 这可能是真的,但如果你像我上面发布的那样使用sed -i,它会删除特定的行并且行号不会像你提到的那样移动。
  • @user946850 我不确定您的评论是否正确,但您是要重新格式化我的 lines.txt 文件为 'd, d' 吗?如果这就是你的意思.. 该死的我没想到!谢谢你的提示!点赞!

标签: linux string sed awk text-files


【解决方案1】:

我在Unix SE 上问了一个类似的问题,得到了很好的答案,其中包括以下 awk 脚本:

#!/bin/bash
#
# filterline keeps a subset of lines of a file.
#
# cf. https://unix.stackexchange.com/q/209404/376
#
set -eu -o pipefail

if [ "$#" -ne 2 ]; then
    echo "Usage: filterline FILE1 FILE2"
    echo
    echo "FILE1: one integer per line indicating line number, one-based, sorted"
    echo "FILE2: input file to filter"
    exit 1
fi

LIST="$1" LC_ALL=C awk '
  function nextline() {
    if ((getline n < list) <=0) exit
  }
  BEGIN{
    list = ENVIRON["LIST"]
    nextline()
  }
  NR == n {
    print
    nextline()
  }' < "$2"

还有另一个 C 版本,性能更高:

【讨论】:

    【解决方案2】:

    这可能对你有用(GNU sed):

    sed 's/.*/&d/' lines.txt | sed -i -f - documents.txt
    

    或:

    sed ':a;$!{N;ba};s/\n/d;/g;s/^/sed -i '\''/;s/$/d'\'' documents.txt/' lines.txt | sh
    

    【讨论】:

      【解决方案3】:

      这是使用sed 的一种方法:

      sed ':a;${s/\n//g;s/^/sed \o47/;s/$/d\o47 documents.txt/;b};s/$/d\;/;N;ba' lines.txt | sh
      

      它使用sed 构建sed 命令并将其通过管道传送到要执行的shell。生成的 sed 命令看起来就像 `sed '3d;5d;11d'documents.txt。

      为了构建它,外部的sed 命令在每个数字后添加一个d;,循环到下一行,分支回到开头(N; ba)。当到达最后一行 ($) 时,所有换行符都被删除,sed ' 被添加到最后一行 d' documents.txt 被添加。然后b 分支出:a - ba 循环到最后,因为没有指定标签。

      以下是使用joincat -n 的方法(假设lines.txt 已排序):

      join -t $'\v' -v 2 -o 2.2 lines.txt <(cat -n documents.txt | sed 's/^ *//;s/\t/\v/')
      

      如果lines.txt 没有排序:

      join -t $'\v' -v 2 -o 2.2 <(sort lines.txt) <(cat -n documents.txt | sed '^s/ *//;s/\t/\v/')
      

      编辑:

      修复了join 命令中的原始版本仅输出documents.txt 中每行的第一个单词的错误。

      【讨论】:

      • 这只是澄清了很多sed。非常感谢。
      【解决方案4】:

      awk oneliner 应该适合你,请参阅下面的测试:

      kent$  head lines.txt doc.txt 
      ==> lines.txt <==
      1
      3
      5
      7
      
      ==> doc.txt <==
      a
      b
      c
      d
      e
      f
      g
      h
      
      kent$  awk 'NR==FNR{l[$0];next;} !(FNR in l)' lines.txt doc.txt
      b
      d
      f
      h
      

      按照 Levon 的建议,我添加了一些解释:

      awk                     # the awk command
       'NR==FNR{l[$0];next;}  # process the first file(lines.txt),save each line(the line# you want to delete) into an array "l"
      
       !(FNR in l)'           #now come to the 2nd file(doc.txt), if line number not in "l",print the line out
       lines.txt              # 1st argument, file:lines.txt
       docs.txt               # 2nd argument, file:doc.txt
      

      【讨论】:

      • 在单行语句中添加一些解释性注释总是好的。我自己是awk 的忠实粉丝,但并不是所有看到这个的人都能理解其中的魔力。
      • 是的。但我认为我得到了他的代码是正确的。被选为最佳答案。
      【解决方案5】:

      好吧,我不会说 Perl 和 bash,我一次又一次地经历痛苦的​​尝试。然而,Rexx 很容易做到这一点;

      lines_to_delete = ""
      
      do while lines( "lines.txt" )
         lines_to_delete = lines_to_delete linein( "lines.txt" )
      end
      
      n = 0
      do while lines( "documents.txt" )
         line = linein( "documents.txt" )
         n = n + 1
         if ( wordpos( n, lines_to_delete ) == 0 )
            call lineout "temp_out,txt", line
      end
      

      这会将您的输出保留在 temp_out.txt 中,您可以根据需要将其重命名为documents.txt。

      【讨论】:

      • 感谢您的宝贵时间。您的回答当然可以解决问题,但我更喜欢 awk 或 sed 解决方案来编写完整的代码。无论如何,请投赞成票! :)
      猜你喜欢
      • 2011-06-14
      • 1970-01-01
      • 2013-05-13
      • 2015-09-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-08
      • 2018-09-21
      相关资源
      最近更新 更多