从另一个文件中读取行号,删除文本文件中的对应行

5
我有一个文本文件,里面包含了一长串需要从主文件中删除的行号。以下是我的数据样例:

lines.txt

1
2
4
5
22
36
400
...

and documents.txt

string1
string2
string3
...

如果我有一个行号的简短列表,我可以轻松地使用sed -i '1d,4d,5d' documents.txt。但是我需要删除很多行号。此外,我可以使用bash/perl脚本将行号存储在数组中,并回显不在该数组中的行。但我想知道是否有内置命令可以做到这一点。任何帮助都将不胜感激。

只是一种观察,对于sed的工作方式一无所知,但如果文件在内存中被修改并删除第一行,则旧的第四行现在将成为当前的第三行。 - Wes Miller
4
为什么不使用 sed 来构建 '#d,...' 字符串呢? - krlmlr
@WesMiller 这可能是正确的,但如果您像我上面发布的那样使用 sed -i,它将删除特定行,并且行号不会像您提到的那样移动。 - javaCity
@user946850,我不确定我是否理解了你的评论,但是你的意思是要将我的lines.txt文件重新格式化为“<line>d,<line2>d”吗?如果是这样的话...天啊,我没有想到!谢谢你的提示!点赞! - javaCity
5个回答

10

awk单行代码对您有用,参见下面的测试:

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

1
在一行代码中添加一些解释性注释总是很好的做法。我自己是awk的忠实粉丝,但并不是每个看到这个的人都能理解其中的奥妙。 - Levon
没错。但我认为我理解了他的代码。被选为最佳答案。 - javaCity

2

我不懂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。

1
谢谢您的时间。您的答案确实解决了问题,但我更喜欢使用awk或sed来解决问题,而不是编写完整的代码。无论如何,感谢您的回答! :) - javaCity

2

以下是使用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命令中的一个bug,原版本只会输出documents.txt中每行的第一个单词。


这解释了很多关于sed的问题。非常感谢。 - javaCity

1
这可能适用于您(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

0

我在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版本,性能更高一些:

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接