【发布时间】:2014-01-18 14:48:17
【问题描述】:
我发现,如果在将文件放入 tar 存档之前按文件扩展名而不是字母顺序对文件列表进行排序,则可以显着提高压缩率(尤其是对于可能有大量 .c 的大型源代码树, .o 和 .h 文件)。
我找不到一种简单的方法来使用 shell 对文件进行排序,该 shell 在每种情况下都能按我期望的方式工作。诸如find | rev | sort | rev 之类的简单解决方案可以完成这项工作,但是文件以奇怪的顺序出现,并且它没有很好地排列它们以获得最佳压缩比。 ls -X 等其他工具不适用于find,并且当文件的文件名中包含多个句点(例如 version-1.5.tar)时,sort -t. -k 2,2 -k 1,1 会出错。另一个快速-n-dirty 选项,使用sed 将最后一个句点替换为/(从不在文件名中出现),然后排序,沿/ 拆分:
sed 's/\(\.[^.]*\)$/\/\1/' | sort -t/ -k 2,2 -k 1,1 | sed 's/\/\([^/]*\)$/\1/'
但是,再次使用 find 的输出(名称中包含 /s,并且在 *nix 中的文件名中允许使用所有其他字符(0 除外))时,这将不起作用。
我发现使用 Perl,您可以编写一个自定义比较子例程,使用与 cmp 相同的输出(类似于 C 中的 strcmp),然后运行 perl 排序函数,传递您自己的自定义比较,即使用 perl 正则表达式易于编写。这正是我所做的:我现在有一个 perl 脚本,它调用
@lines = <STDIN>;
print sort myComparisonFunction @lines;
然而,perl 不像 bash 那样可移植,所以我希望能够使用 shell 脚本。此外,find 没有在目录名称后面加上 /,因此脚本认为目录与没有扩展名的文件相同。理想情况下,我想让tar 首先读取所有目录,然后是常规文件(并对它们进行排序),然后是我可以通过
cat <(find -type d) <(find -type f | perl exsort.pl) <(find -not -type d -and -not -type f) | tar --no-recursion -T - -cvf myfile.tar
但我仍然遇到这样的问题,要么我必须每次都输入这个怪物,要么我有一个用于这个长行的 shell 脚本和一个用于排序的 perl 脚本,而 perl 并不是随处可用,所以把所有东西都塞进一个 perl 脚本也不是一个很好的解决方案。 (我主要关注较旧的计算机,因为现在所有现代 Linux 和 OSX 都带有足够新的 perl 版本。
我希望能够将所有内容放在一个 shell 脚本中,但我不知道如何将自定义函数传递给 GNU 排序工具。 我是不是运气不好,必须使用一个 perl 脚本?或者我可以用一个 shell 脚本来做到这一点吗?
编辑:感谢 Schwartizan 变换的想法。我使用了一种稍微不同的方法,使用sed。我的最终排序程序如下:
sed 's_^\(\([^/]*/\)*\)\(.*\)\(\.[^\./]*\)$_\4/\3/\1_' | sed 's_^\(\([^/]*/\)*\)\([^\./]\+\)$_/\3/\1_' | sort -t/ -k1,1 -k2,2 -k3,3 | sed 's_^\([^/]*\)/\([^/]*\)/\(.*\)$_\3\2\1_'
这会处理文件名中的特殊字符(例如 *),并首先放置没有扩展名的文件,因为它们通常是文本文件。 (生成文件、复制、自述文件、配置等)。
附:如果有人想要我原来的比较功能或认为我可以改进它,这里是:
sub comparison {
my $first = $a;
my $second = $b;
my $fdir = $first =~ s/^(([^\/]*\/)*)([^\/]*)$/$1/r;
my $sdir = $second =~ s/^(([^\/]*\/)*)([^\/]*)$/$1/r;
my $fname = $first =~ s/^([^\/]*\/)*([^\/]*)$/$2/r;
my $sname = $second =~ s/^([^\/]*\/)*([^\/]*)$/$2/r;
my $fbase = $fname =~ s/^(([^\.]*\.)*)([^\.]*)$/$1/r;
my $sbase = $sname =~ s/^(([^\.]*\.)*)([^\.]*)$/$1/r;
my $fext = $fname =~ s/^([^\.]*\.)*([^\.]*)$/$2/r;
my $sext = $sname =~ s/^([^\.]*\.)*([^\.]*)$/$2/r;
if ($fbase eq "" && $sbase ne ""){
return -1;
}
if ($sbase eq "" && $fbase ne ""){
return 1;
}
(($fext cmp $sext) or ($fbase cmp $sbase)) or ($fdir cmp $sdir)
}
【问题讨论】:
-
"Perl 不如 bash 便携..." 什么???
-
@ThisSuitIsBlackNot 在旧计算机上。例如,旧版本的 Mac OS 默认不附带它。
-
这和便携不一样。 Windows 也不附带 bash。
-
+1 表示整体问题和有关焦油效率的信息!。您能否提取扩展名,将其附加为临时第一个字段,并使用
-k1 -k2(k2 是完整路径名)对其进行排序,然后从传递给 tar 的最终列表中删除 tmp 字段? -
find 可以包含 /:
find PATHS -type d -printf '%p/\n' -o -print(在非 GNU 中也可以使用 -exec printf 进行查找,但更难)
标签: regex perl bash shell sorting