我正在尝试递归地查找所有包含一个大小小于 10MB 的 mp4 文件的目录。
条件是,
- 目录中只能有一个 mp4 文件。
- mp4 文件不能超过 10MB。
我正在使用的命令是
% find . -type f -name "*.mp4" -size -10M | cut -d/ -f2 | sort | uniq -c | grep "^ 1"
我不确定发生了什么,但此命令没有返回准确的结果。
经过进一步调查,我发现以下命令有效。
find . -type 'f' -name "*.mp4" -printf '%h\n' | sort | uniq -c | grep -E "\s+1\s"| cut -c 9-
但是当我添加-size -10000000c
到 mix 时,它发现文件中有一个 mp4 文件大小小于 10MB,但还有其他 mp4 文件大小大于 10MB。我的意思是我提到的命令没有考虑大小大于 10MB 的 mp4 文件。我认为这个问题可以分为两步。
查找所有包含一个 mp4 文件的目录。这是通过上述命令完成的。
检查文件是否小于10MB。
我可以使用以下命令获取目录中单个 mp4 文件的文件大小。
find . -type 'f' -name "*.mp4" -printf '%h\n' | sort | uniq -c | grep -E "\s+1\s" | cut -c 9-| xargs -I {} -n 1 /usr/bin/du -a "{}" | grep -v ".mp4$"
答案1
find
至少对于 GNU ,-size -10M
对于大小四舍五入到下一个兆字节严格小于 10(即 9 或更小)的文件来说是这样。
不会选择大小为 9 x 1024 x 1024 + 1 = 9437185 字节的文件,因为该文件已四舍五入为 10MiB,因此不会 < 10。
对于严格小于 10MB 的文件(1 MB 是 1,000,000 字节,不要与 1 Mebibyte == 1,048,576 字节混淆),因此大小为 0 到 9,999,999,请使用:
find . -size -10000000c
对于严格小于 10MiB 的文件,因此大小从 0 到 10485759:
find . -size -10485760c
现在,要在 GNU 系统上获取包含一个且仅一个这些文件的目录,您可以执行以下操作:
LC_ALL=C find . -name '*.mp4' -type f -size -10000000c -printf '%h\0' |
LC_ALL=C sort -z |
LC_ALL=C uniq -zu |
tr '\0' '\n'
在哪里
find
打印h
这些文件的 ead(目录名),以 NUL 分隔(请注意,LC_ALL=C
报告所有以其他方式结尾的文件名,.mp4
即使这些文件名在当前语言环境中不是有效文本)。sort
对它们进行排序uniq
(再次,以LC_ALL=C
避免文件名在区域设置中不是有效文本的问题,以及字符未完全定义顺序的其他问题)。uniq -zu
仅报告唯一的。
文件列表在 NUL 分隔之间传递,因为 NUL 是文件路径中唯一不能出现的字符。我们最后只将这些 NUL 转换为换行符以tr
供人类使用。
使用zsh
,您还可以执行以下操作:
print -rC1 -- **/*(NFe['()(( $# == 1 )) $REPLY/*.mp4(N.L-10000000Y2)'])
在哪里:
print -rC1 --
print
s 其论点r
aw on1
C
olumn**/
是任意数量的子目录。*(NF...)
是任何文件名(不包括隐藏文件),但由N
,F
,e
... glob 限定符进一步限定。N
:启用nullglob
该 glob,以便它扩展为空,而不是在不匹配时返回错误。F
:选择全部目录(除了和F
之外至少有一个条目的目录)。.
..
e[code]
:选择成功的文件code
。() {body} arguments
是一个带有多个参数的匿名函数。- 这
{body}
是(( $# == 1 ))
返回的算术评估真的如果该匿名函数的参数数量为 1。 $REPLY
里面code
是正在考虑的文件(这里是目录)的路径。*.mp4(qualifiers)
:(非隐藏)mp4
文件进一步限定。.
:仅限常规文件(如find
's-type f
)。L-10000000
:文件严格小于 10MB。Y2
:找到2个文件后停止作为优化。
请注意,它不考虑.
(当前工作目录本身)。如果您希望考虑它,请替换**/*
为{.,**/*}
。
现在,正如您已经澄清的那样,如果您想查找仅包含一个 mp4 文件的目录,并且该文件是常规文件(不是目录、符号链接...)并且小于 10MB(因此,例如排除包含以下内容的目录): 5MB 和 15MB mp4 文件,尽管它只有一个小于 10MB 的 mp4 文件,因为无论大小,它总共有多个 mp4),仍然zsh
:
print -rC1 -- **/*(NFe['
() {
(( $# == 1 )) && ()(($#)) $1(N.L-10000000)
} $REPLY/*.mp4(NY2)
'])
对于 GNUfind
和 GNU awk
(或任何可以处理 NUL 分隔记录的 awk),这可能是:
LC_ALL=C find . -name '*.mp4' -printf '%h\0%s\0%y\0' |
awk -v RS='\0' '
{
getline size; getline type
total[$0]++
if (size < 10e6 && type == "f") found[$0]++
}
END {for (dir in found) if (total[dir] == 1) print dir}'
答案2
find
太棒了,我一直使用它来完成比这复杂得多的任务......但有时弄清楚所有的find
选项并让它做你想做的事情,然后使用其他程序,如,,sort
等是一个有点像 PITA,似乎更简单的是,编写自己的自定义工具,用一种具有体面库的语言来完成您想要的操作,用于递归搜索目录,并使用体面的编辑器而不是 shell 的命令行编辑器来完成此操作。grep
uniq
所以你最终会写出类似以下内容的另一个小变体。更改wanted
子例程,就会更改该find
函数发现的内容。这个打印出一个目录列表,其中至少包含一个大小 <= 10MiB 的常规文件,文件名以 结尾.mp4
:
$ cat find-mp4-1.pl
#!/usr/bin/perl
use strict;
use File::Find;
my %found;
sub wanted {
-f $_ && -s $_ <= 10485760 && /\.mp4\Z/s &&
$found{$File::Find::dir . "/"}++;
};
# Search all directories listed on command line.
# Default to current directory
find(\&wanted, @ARGV ? @ARGV : '.');
print join("\n", sort keys %found), "\n" if %found;
我已经写了很多File::Find
这样的小脚本,以至于我都记不清了。
示例运行:
$ mkdir videos
$ touch video1.mp4 videos/video2.mp4
$ ./find-mp4-1.pl
./
./videos/
然后您意识到有时使用 NUL 分隔的输出会很有用,因此需要一个-0
选项。一旦完成,认为能够在命令行上指定所需的大小会很好,并且文件名模式匹配也是如此,并且不区分大小写搜索的选项会很棒,因此能够使用“人类可读”的大小,我可以通过预编译正则表达式并仅匹配文件名的基本名称部分(谁不喜欢一点点过早的优化)来使其更快一点,并且......得意忘形并这样做:
$ cat find-mp4-2.pl
#!/usr/bin/perl
use strict;
use File::Find;
use Number::Bytes::Human qw(parse_bytes);
use Getopt::Std;
my %found;
my %opts;
$Getopt::Std::STANDARD_HELP_VERSION=1;
our $VERSION='0.2';
getopts('0:s:r:i',\%opts) ||
die "Usage: $0 [-0] [-s size] [-r regex] [-i] [directory...]\n";
my $sep = $opts{0} ? "\0" : "\n";
my $size = $opts{s} // '10MiB';
my $regex = $opts{r} // '\.mp4\Z';
$size = parse_bytes($size);
# pre-compile the regex: case insensitive or case sensitive?
$regex = $opts{i} ? qr/$regex/si : qr/$regex/s;
sub wanted {
-f $_ && -s $_ <= $size && $File::Find::name =~ /$regex/ &&
$found{$File::Find::dir . "/"}++;
};
find(\&wanted, @ARGV ? @ARGV : '.');
print join($sep, sort keys %found), $sep if %found;
笔记:文件::查找和获取选择::标准是核心 Perl 模块并包含在 Perl 中。
数量::字节::人类不是,它需要单独安装(在 Debian 及其衍生版本上:sudo apt-get install libnumber-bytes-human-perl
。其他发行版也可能将其打包。否则,请使用 来安装cpan
)。
或者像一些原始的穴居人一样删除use Number::Bytes::Human qw(parse_bytes);
和行并以字节为单位指定文件大小。$size = parse_bytes($size);
然后你会想“嗯......也许我应该使用Getopt::长而不是Getopt::Std
能够处理--long
选项,并且有一个-c
选项来输出目录中的匹配数可能会很有用,并且需要文档和......”。也许你甚至开始修改它来做到这一点,然后你才意识到,“不!这是疯狂。制作工具很有趣,但足够了。”
你知道,就像一些假设的例子一样,一个足够疯狂的人可能会做的事情,没有说出任何名字或任何东西。我可以随时停下来。我的赞助商的电话号码在哪里?我想我需要给他们打电话。
顺便说一句,只打印包含以下内容的目录确切地一个匹配的视频,您可以将该print join ...
行更改为:
foreach (sort keys %found) {
print "$d\n" if $found{$_} == 1
};
(或print "$d$sep" ...
第二个版本)
请注意,这将打印包含多个 .mp4 文件的目录,其中只有一个 <= 10MB。要排除这些,您必须修改wanted
子例程,以便它们永远不会进入散列%found
(或在函数完成之前从散列中删除find()
)。也许可以使用另一个散列来跟踪找到多个 .mp4 文件的目录,例如:
sub wanted {
next unless -f $_ && $File::Find::name =~ /\.mp4\Z/s;
my $d = $File::Find::dir . '/';
$seen{$d}++;
if ($seen{$d} > 1) {
delete $found{$d};
} else {
$found{$d} = 1 if -s $_ <= 10485760;
}
};
并将该my %found;
行更改为my (%found, %seen);