递归列出包含一个或多个 jpg 图像文件的所有目录

递归列出包含一个或多个 jpg 图像文件的所有目录

我正在尝试整理我的照片,由于各种历史原因,这些照片分散在我的系统中。为了让我能够开始这项任务,我一直在尝试使用命令行来构建包含一个或多个 jpg 文件的所有目录的列表。我确信我不必担心寻找其他图像文件格式,但我确实必须允许 jpg 以大小写形式出现。

我希望每个目录名称在最终列表中只出现一次。举个例子,如果我有以下目录,每个目录都包含一个或多个 jpg 或 JPG 文件......

~Mike/Pictures
~Mike/Pictures/London/Olympics
~Mike/Pictures/London
~Mike/Pictures/London/Holiday
~Mike/Photos
~Mike/Family History/Swaine

我希望每个目录只列出一次结果 - 无论它可能包含多少图像文件 - 最好先排序然后写入文件

~Mike/Family History/Swaine
~Mike/Photos
~Mike/Pictures
~Mike/Pictures/London
~Mike/Pictures/London/Holiday
~Mike/Pictures/London/Olympics

我的命令行技能还达不到这个水平!我可以使用许多更简单形式的单个命令,但是一旦它们变得复杂和/或必须通过管道传输,事情往往会出错。

答案1

假设 JPEG 图像文件的后缀为.jpg.JPG

find "$HOME" -type f \( -name '*.jpg' -o -name '*.JPG' \) \
    -exec sh -c 'for d; do dirname "$d"; done' sh {} + | sort -u -o jpeg_dirs.txt

这依赖于您的目录名称中没有包含换行符的时髦目录名称。

使用 GNU find

find "$HOME" -type f \( -name '*.jpg' -o -name '*.JPG' \) -printf '%h\n' | sort -u -o jpeg_dirs.txt

这些find命令将查找您的主目录下的所有 JPEG 图像,并打印找到它们的目录的名称。将sort -u获取此目录名称列表,对其进行排序并删除重复项。结果将写入jpeg_dirs.txt当前目录中的文件中。


2021 年初(3.3 年后)回顾这一点,我有点畏缩,因为我上面的解决方案虽然本身并没有错,但有点落后。它还对“好的文件名”(没有换行符)做出了明显的假设。

当你用来find搜索目录时,不要像我上面那样搜索常规文件;实际上搜索目录。一旦我们有了目录,我们就可以查看每个目录,看看是否有匹配的文件*.jpg*.JPG(其他文件名后缀很容易添加):

find "$HOME" -type d -exec bash -O nullglob -O dotglob -O extglob -c '
    for dirpath do
        set -- "$dirpath"/*.@(jpg|JPG)
        [ "$#" -eq 0 ] || printf "%s\n" "$dirpath"
    done' bash {} +

这会从主目录向下查看每个目录,并尝试扩展*.@(jpg|JPG)每个目录中的通配模式。该模式也可以写为两个单独的模式,*.jpg并且*.JPG与我们正在查找的所有文件相匹配。如果一个名称匹配,我们就假设这是一个我们想要输出其名称的目录。这会给仅包含以下内容的目录带来误报子目录带有这些后缀。

我们运行内部脚本的 shell 选项bash允许我们匹配隐藏名称 ( dotglob),允许通配模式在不匹配任何内容时完全消失而不是保持未展开状态 ( nullglob),并允许我们使用ksh-inspired 扩展通配模式@(...|...)

使用zsh外壳:

typeset -U list=(~/**/*.(jpg|JPG)(.DN:h))
print -rC1 $list

这将创建一个数组变量 ,list它具有仅存储唯一元素的属性。它被初始化为扩展文件名通配模式的结果。该模式匹配主目录中或主目录下的所有 JPEG 图像文件,:h最后的 会从生成的路径名中删除实​​际文件名。使.模式仅匹配常规文件,并且D和的作用N类似于.dotglobnullglobbash

答案2

一种简单的方法是列出所有.jpg文件,然后去掉文件的基本名称(最后斜杠后面的部分),并删除重复项。您可以使用sed删除最后一个斜杠之后的每行部分。有一个删除重复项的命令,称为uniq,但它假定输入已排序;如果你无论如何都需要排序,你可以让其sort进行唯一化。

find ~Mike -iname '*.jpg' | sed 's!/[^/]*$!!' | sort -u >directories_with_jpeg_files.txt

这假设所涉及的目录或文件的名称中没有换行符。带有换行符的文件名在正常情况下不会出现,但请注意文件名是否可能由敌对者选择(例如,如果您正在处理已上传到服务器的文件并且上传者可以选择文件名) 。

如果存在包含大量 JPEG 文件的目录,而没有包含 JPEG 文件的目录不多,则此方法会花费大量时间来报告冗余文件。一旦 find 在目录中找到某些内容,就无法告诉它快捷方式。但是您可以将 find 限制为目录并告诉它在每个目录中搜索 JPEG 文件。然而,这会增加不包含 JPEG 文件的目录的成本,因此如果有许多 JPEGless 目录,性能可能会很差。

find ~Mike -type d -exec sh -c '
    for d do
      set -- "$d/*.[Jj][Pp][Gg]";
      if [ -e "$1" ]; then printf %s\\n "$d"; fi
    done
' sh {} + | sort -u >directories_with_jpeg_files.txt

或者,在 zsh 中,您可以使用**通配符递归遍历目录,(#i)不区分大小写地匹配后面的路径组件,从而使整个目录树中的模式**/(#i)*.jpg匹配*.jpgand *.JPG(等等)。在 glob 限定符中.Jpg添加历史修饰符h以提取目录部分。将其填充到数组变量中,并使用参数扩展标志dirs=(…)提取该数组的唯一元素。u

set -o extendedglob # for (#i); best in ~/.zshrc
dirs=(~Mike/**/(#i)*.jpg(:h))
print -lr -- ${(u)dirs} >directories_with_jpeg_files.txt

与上面的按目录检查方法等效的是使用eglob 限定符。

print -lr ~Mike/**/*(/e\''set -- $REPLY/*.(#i)jpg(N[1]); (($# != 0))'\') >directories_with_jpeg_files.txt

答案3

find . -iname '*.jpg' -execdir sh -c 'pwd' _ {} + | sort -u > dirs_with_jpegs.txt

find假设您实现了支持,应该可以很好地工作-execdir(可能确实如此)。-execdir在找到的文件所在的目录中执行命令。在本例中,我们执行命令pwd,该命令打印目录的名称。我们sh -c用 strip 参数包装命令。 (一些(全部?)实现find需要{}参数替换,这将是当前目录中的 jpeg 文件列表。我们想忽略该列表,只打印目录。)

相关内容