从文件中删除具有二进制模式字符串的行

5

我有两个文件。文件A包含N行文本,文件B包含一个长度为N的0和1的二进制模式字符串。

我想要从文件A中删除与文件B相同行号的行,而文件B中包含0。

我读到可能可以使用awk来完成这项工作,但我不知道如何使用它。

这些文件非常长,例如有2000行(它们是视频跟踪文件)。

例如:

文件A:

Line 1: 123456
Line 2: 789012
Line 3: 345678
Line 4: 901234

文件B:

Line 1: 1
Line 2: 0
Line 3: 0
Line 4: 1

执行后:
文件A:
Line 1: 123456
Line 2: 901234

3
“Line 1:”等内容并不是文件的一部分,对吗? - Benjamin W.
没错,第一行等并不是我的文件的一部分,冒号后面才是代码开始的地方(这只是为了澄清我想做什么)。感谢大家的回答!我今天会开始着手处理,并告诉你们我最终选择了哪个方案。有很多有趣的提议! - César A
我希望它能够针对每个文件进行操作,而不考虑每行包含的内容。如果另一个文件中相同的行号为0,则希望删除该行,因为使用不同的视频将会产生不同的视频跟踪结果。我想你已经考虑到了这一点。 - César A
8个回答

6
您可以使用“粘贴”和“剪切”来完成此操作:
paste fileB fileA | grep '^1' | cut -f2-
  • paste fileB fileA - 将文件内容并排粘贴在一起,以制表符分隔
  • grep '^1' - 过滤以1开头的行
  • cut -f2- - 提取我们需要的内容

cutpaste默认使用制表符作为分隔符。

这与Benjamin的解决方案非常相似。这里的一个小优点是,即使fileA每行有多个字段,它也能正常工作。


2
好的,我的解决方案如果在fileA中有一个制表符就会出现问题,我必须为pastecut使用不同的分隔符。 - Benjamin W.
1
最终我使用了这个命令,但是我想在删除行之后保存它,所以我对它进行了修改:将fileB和fileA粘贴起来,然后使用grep '^1'和cut -f2-命令,最后将结果保存到finalFile.txt文件中。 - César A

3

这里有很多有趣的答案。以下是一个 Bash 解决方案:

while IFS= read -r -u3 line; IFS= read -r -u4 bool; do 
    ((bool == 1)) && printf "%s\n" "$line"
done 3<fileA 4<fileB

这种方法比其他解决方案慢得多。


3
您可以使用“装饰 - 过滤 - 取消装饰”模式:

Schwartzian变换

paste fileA fileB | grep -v '0$' | cut -f1

这将会把每个文件的行并排打印 (paste),然后过滤以数字 0 结尾的行 (grep),最后从第二个文件中删除这些行 (cut)。
如果 fileA 包含了用于 pastecut 的分隔符 (默认为制表符),那么上述方法将会失败。为了避免这种情况,我们可以交换文件的位置(参见 codeforester's answer),或者使用类似如下的方式:
paste fileA fileB | sed -n '/1$/s/\t.$//p'

(如果行以 1 结尾,则删除制表符和最后一个字符,然后打印)或
paste fileA fileB | grep -Po '.*(?=\t1$)'

(仅匹配以1结尾的行,使用零宽度先行断言来排除制表符和1的匹配);最后一个解决方案需要支持Perl兼容正则表达式(PCRE)的grep,例如GNU grep。

3
假设您的输入文件中不存在第1行:等内容,您只需要执行以下操作:
awk 'NR==FNR{a[NR]=$0;next} a[FNR]' fileB fileA

1
我在Windows上使用了这个,因为我安装了“awk”。谢谢你! - César A

2
一条 awk 命令可以从两个文件中读取数据。
awk '(getline flag < "fileB") > 0 && flag' fileA

阅读 fileA 中的每一行后,从 fileB 中读取一行到变量标志中,并测试其整数值是否为真。对于真值,将输出来自fileA 的行。
根据您的 awk 版本,您可能需要使用 int(flag)flag+0 强制将该值视为整数而不是普通的非空字符串。

嗯,我添加了对int的调用,因为(至少在我用于测试的macOS上的BSD版本中)当我仅使用flag时,每行都会被打印出来。 - chepner
我不确定我理解getline flag < "fileB"(getline flag < "fileB") > 0之间的区别。 - chepner
1
关于 getline,显然我只需要更仔细地阅读文档。谢谢。 - chepner

2

这是另一种使用paste/awk的解决方案。如果数据中出现制表符,请寻找另一个分隔符。

paste file2 file1 | awk -F'\t' '$1{print $2}' 

1

编辑: 如果Line 1Line 2不是您的文件1和文件2的一部分,则以下内容可能会有所帮助。

awk 'FNR==NR{a[FNR]=$0;next} $0!=0{print a[FNR]}' filea fileb

解决方案2: 先读取文件b,然后再读取文件a。

awk 'FNR==NR{if($0!=0){a[FNR]=$0};next} a[FNR]' fileb filea


如果OP的文件中有字符串line1、line2,那么Solution 1st的替代方案如下。以下awk命令也可以帮助解决问题。
awk '
FNR==NR{
  a[FNR]=$NF;
  next}
$NF!=0{
  printf("%s%s\n","Line " ++count": ",a[FNR])
}' filea fileb

1
我认为“Line 1:”等不是文件的一部分。 - codeforester
@codeforester,很酷,当然现在也添加了EDIT解决方案,以处理行字符串不是文件的一部分的情况。 - RavinderSingh13
1
这里的 $NF!=0 可以替换为 $NF - karakfa

0

pastesed 组合:

paste -d'\n' fileB fileA | sed -n '/^1$/{n;p}'
123456
901234

你交错地排列文件:

1
123456
0
789012
0
345678
1
901234

然后你使用sed打印出紧跟一个仅包含1的行的后续行。但是,如果fileA中存在仅由1组成的条目,则此命令将无法正常运行。如果是这种情况,则必须使用以下考虑当前处理奇偶行的sed命令:

paste -d'\n' fileB fileA | sed -n '1~2{/^1$/{n;p}}'

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