![递归地从文件中删除重复的行,但保留一行 - 使行在文件中唯一](https://linux22.com/image/176910/%E9%80%92%E5%BD%92%E5%9C%B0%E4%BB%8E%E6%96%87%E4%BB%B6%E4%B8%AD%E5%88%A0%E9%99%A4%E9%87%8D%E5%A4%8D%E7%9A%84%E8%A1%8C%EF%BC%8C%E4%BD%86%E4%BF%9D%E7%95%99%E4%B8%80%E8%A1%8C%20-%20%E4%BD%BF%E8%A1%8C%E5%9C%A8%E6%96%87%E4%BB%B6%E4%B8%AD%E5%94%AF%E4%B8%80.png)
我有很多文件夹,文件夹中包含文件。同一行可能会在单个文件和/或多个文件中出现多次。文件未排序。因此,有些行在多个文件中重复,并且这些文件位于不同的文件夹中。
我想删除重复的行并在所有文件中仅保留其中一行。文件结构和名称也应保持相同。
我已经尝试过,但仅在每个文件中而不是在所有文件中使唯一。此代码使每个文件中的行都是唯一的并保留文件名:
for i in $(find . -type f); do
awk '!seen[$0]++' "$i" > tmp_file
mv ./tmp_file "$i"
done
问题:如何使所有子文件夹中的所有文件中的行保持唯一,同时保留文件结构和名称?
这是我的文件示例。为了简单起见,我在这里仅列出文件,但文件位于相同或不同的文件夹中。
输入:
$ cat File-1
1
2
3
1
$ cat File-2
2
3
4
1
$ cat File-3
2
4
5
6
输出:
$ cat File-1
1
2
3
$ cat File-2
4
$ cat File-3
5
6
就我而言,保留第一次出现的行是首选,但不是必需的(保留的行可以位于任何文件中)。
答案1
#!/usr/bin/perl
use File::Find;
my $headdir="/some/path";
my @files=();
my $lines={};
find( { wanted => sub { push @files, $_ }, no_chdir => 1 }, $headdir );
foreach my $file (@files) {
next unless(-f $file);
system "cp $file $file". ".old";
open(my $fhin, "$file".".old");
open(my $fhout, ">$file");
while(<$fhin>) {
if(not defined $lines->{$_}) {
print $fhout $_;
$lines->{$_} = 1;
}
}
close($fhin);
close($fhout);
#optional: system("rm $file".".old");
}
编辑:(仅)使用问题中提到的文件进行测试,需要对代码进行微小的更改
答案2
仅当要处理的文件数量小到足以find
运行awk
一次时,下面的操作才有效。它还假设您可以复制整个文件树(即您不受存储限制)。
假设您的文件树位于orig
目录中:
$ cp -pr orig tmp
$ cd tmp
$ find . -type f -exec awk '
BEGIN { print ARGC }
FILENAME != fn {
close( "../orig/"fn )
printf "" > ( "../orig/"FILENAME )
}
!seen[$0]++ { print > ( "../orig/"FILENAME ) }
{ fn = FILENAME; }' {} +
一旦您对结果满意,您就可以rm -r tmp
。
print ARGC
awk
用于显示被调用的次数。ARGC
是命令行参数数组中的元素数量(包括脚本本身);看到它多次打印意味着全局行重复数据删除失败。
(事实上,如果您可以计算要处理的文件总数,则可以更改该块if ( (ARGC - 1) < total_number_of_files) exit
以确保在awk
要多次调用时不会修改任何文件)。