就地递归排序并删除重复项

就地递归排序并删除重复项

我想对所有子目录中的所有文件进行排序。我在 256 个目录中有 65536 个文件,每个文件一行包含一个单词,并且每个文件都包含重复项。

我想要的是使用 -u 选项对这些进行排序(我不知道为什么,但如果我将排序传递给 uniq -u 命令,它实际上会删除重复的唯一行,这很奇怪,但无论如何),但我不这样做想要任何输出文件,我想排序读取内存中的文件,然后覆盖它们。我尝试了 -o 选项,但它需要一个文件名。

有没有办法递归地执行此操作?

谢谢 :)

答案1

那可以只是:

find . -type f -size +1c -exec sort -uo {} {} ';'

(这里跳过小于 2 字节大的文件,因为您至少需要 3 个字节来制作两个不同的行,或者可能需要 2 个"\nx"字节,其中空行后面跟着一个不定界的行1)。

请注意,默认排序顺序sort基于区域设置的排序规则算法。

两行即使字节与字节不相同,也可以进行相同的排序,特别是当这些行包含不形成有效字符的字节序列时,并且在 GNU 系统(例如 Debian)上,对排序顺序不相同的字符进行排序定义的。

你可以做:

LC_ALL=C find . -type f -exec sort -uo {} {} ';'

相反,在基于 ASCII 的系统(例如适用于所有体系结构和内核的 D​​ebian)上,将按字节值而不是语言环境排序顺序(或 IOW,C 语言环境的排序规则基于字节值)对行进行排序,并且应该保证两个字节不同的行不会排序相同。

sort这对每个文件运行一次调用。如果文件相当短,为了加快速度,您可以这样做zsh

zmodload zsh/mapfile
for f (**/*(N.)) print -rC1 -v 'mapfile[$f]' - ${(fou)mapfile[$file]}

sort这避免了多次运行外部命令,而是使用其o参数u扩展标志来对行进行排序和唯一。请注意,它会删除输入中的空行(如果有),并跳过隐藏文件(D如果需要,请添加 glob 限定符)。

与 GNU 相反sort -uzsh不会将字节与字节不相同的两个字符串视为重复(即使它们排序相同),因此您不需要将语言环境修复为 C 。

$ 区域设置标题字符映射
英国的英语语言环境
UTF-8
$a=(

答案2

$ find . -type f -exec perl -MList::MoreUtils=uniq -i -0 -n -e \
    'print join("\n", uniq split /\n/), "\n"' {} +

这用于find将文件名传递给perl使用-i就地编辑选项的脚本,并且列表::更多实用工具uniq为perl提供一个函数(perl有sort内置的,但没有uniq)。 perl 的选项与的选项-n几乎执行相同的操作- 它迭代每个输入记录,但默认情况下不打印任何内容(即,它只打印显式打印的内容)。sed-n

该脚本将输入记录分隔符设置为 NUL(使用该-0选项),以便它可以一次性吞入每个文件。然后它按换行符分割每个输入文件,并以唯一的排序顺序打印它们。

List::MoreUtils位于liblist-moreutils-perlDebian(以及 Ubuntu、Mint 等)上的软件包中,并且在 Fedora、Centos 甚至 RHEL 上可能有类似的软件包名称。其他发行版可能也打包了它。否则从 CPAN 安装它。

如果您不想(或不能)安装List::MoreUtils,您可以使用关联数组(也称为“哈希”)。例如

$ find . -type f -exec perl -i -0 -n -e \
    '%uniq = map {$_ => 1} split /\n/;
     print join("\n", sort keys %uniq), "\n"' {} +

我会首先在一些垃圾文件上运行其中任何一个,以确保它能够满足您的要求。和/或在没有-i选项的情况下测试它,以便它只打印到标准输出。我在一个名为的文件上测试了它junk,其内容如下:

6
5
5
4
3
2
1
1

运行上述任一版本后,junk包含:

1
2
3
4
5
6

如果您希望它们按相反的顺序排列,您可以在or函数reverse之前添加。例如。sortuniqprint join("\n", reverse sort keys %uniq),"\n"


或者,如果您有海绵从 Joey Hess 的moreutils安装中,您可以使用findshellsort -usponge

find . -type f -exec sh -c \
  'for f in "$@"; do sort -u "$f" | sponge "$f" ; done' find-sh {} +

这将比 perl 版本慢得多,因为必须shforksort一次sponge对于每个输入文件,而不是perl仅分叉一次(*)来find处理所有输入文件。

(*) 假设所有文件名都适合一个命令行。在 Linux 上,这大约相当于 200 万个字符。如果所有文件名组合起来都比这个长,则可能需要 fork perl 两次或三次(或者可能更多)。

这同样适用于sh由 分叉,它可能需要分叉多次 - 但与分叉和find的成本相比,分叉 sh(或 perl)几次的成本可以忽略不计。sortsponge 每个文件一次。节省时间来自于使用+withfind ... -exec ... {}而不是\;

相关内容