递归搜索所有目录下有一个小于10MB的mp4文件

递归搜索所有目录下有一个小于10MB的mp4文件

我正在尝试递归地查找所有包含一个大小小于 10MB 的 mp4 文件的目录。

条件是,

  1. 目录中只能有一个 mp4 文件。
  2. 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 文件。我认为这个问题可以分为两步。

  1. 查找所有包含一个 mp4 文件的目录。这是通过上述命令完成的。

  2. 检查文件是否小于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 -- prints 其论点raw on 1 Column
  • **/是任意数量的子目录。
  • *(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 的命令行编辑器来完成此操作。grepuniq

所以你最终会写出类似以下内容的另一个小变体。更改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);

相关内容