如何在 OSX 上找到包含特定类型的 x 个文件的文件夹并输出这些路径

如何在 OSX 上找到包含特定类型的 x 个文件的文件夹并输出这些路径

我有这个适用于 OSX 的脚本,用于查找仅包含一个文件的文件夹,如果该文件是音频文件,则输出音频文件的路径

find "$1" -type d -exec sh -c '[[ $(find "$0" -mindepth 1 | wc -l) -eq 1 ]] 
&& [[ $(find "$0" -mindepth 1 -type d | wc -l) -eq 0 ]]  
&& find "$0"' {} \; |egrep ".mp4|.mp3|.ogg|.flac|.wma|.m4a"

即使用类似

./findodd.sh /Users/paul/Music

但我需要做两点改进:

  1. 我可以进行哪些更改,以便它列出包含 2 个文件、3 个文件等的文件夹中的文件,如果可以将其作为参数传递就更好了

  2. 当前,它查找仅包含一个文件的文件夹,并且该文件必须是音频文件,但我真正希望它做的是查找仅包含一个音频文件的文件夹,即,如果文件夹包含三个文件但只有一个是音频文件,我希望列出该音频文件。

谢谢保罗

答案1

$ find
.
./folder3
./folder3/quux.txt
./folder1
./folder1/test.mp3
./folder1/test.txt
./folder1/test.wma
./folder2
./folder2/bar.txt
./folder2/foo.txt
./folder2/test.ogg

示例运行:

$ ./findaudio.sh /tmp/findaudio 1
/tmp/findaudio/folder2/test.ogg

$ ./findaudio.sh /tmp/findaudio 2
/tmp/findaudio/folder1/test.mp3
/tmp/findaudio/folder1/test.wma

# The first parameter defaults to the current directory and
# the second parameter defaults to 1 so this works as well:
$ ./findaudio.sh
./folder2/test.ogg

下面是代码:

#!/bin/bash

shopt -s nullglob

find "${1:-.}" -type d | while read dir; do
        files=( "${dir}"/*.{mp4,mp3,ogg,flac,wma,m4a} )
        IFS=$'\n'
        (( ${#files[@]} == ${2:-1} )) && echo "${files[*]}"
done

它遍历给定目录的所有子目录,并使用通配符将当前子目录的所有音频文件名读入数组files。如果数组的大小与所需的值匹配,它只会打印出以换行符分隔的文件名。

编辑:这是我之前的方法,基于您想要打印文件夹而不是相关文件名的假设。我将把它留在这里以供将来参考。

$ find . \( -name '*.ogg' -o -name '*.wma' -o -name '*.mp3' \) -printf "%h\n" | uniq -u
./folder2

这样做的目的是查找具有所列音频扩展名的所有文件,并仅打印其目录部分而不是完整路径。这将为您提供所有音频文件的父文件夹列表。uniq跳过非唯一行,这将为您提供所需的结果,即仅打印仅包含一个音频文件的文件夹。

从理论上讲,这也应该比你之前的尝试要快得多。

您可以通过计算重复的行数并仅打印符合您要求的计数的文件夹来改进此方法以满足第一点。一个简单的解决方案是:

$ find . \( -name '*.ogg' -o -name '*.wma' -o -name '*.mp3' \) -printf "%h\n" | uniq -c | awk -v count=1 '$1==count'
1 ./folder2

$ find . \( -name '*.ogg' -o -name '*.wma' -o -name '*.mp3' \) -printf "%h\n" | uniq -c | awk -v count=2 '$1==count'
2 ./folder1

uniq但将管道的部分和右侧融合在一条线上可能会更好awk

答案2

第二次尝试

好的,在我自己的“音乐”文件夹中尝试之后,这是针对您两个请求的解决方案:

COMMAND='[[ $(find "$0" -maxdepth 2 |egrep "\.mp4|\.mp3|\.ogg|\.flac|\.wma|\.m4a"| wc -l) == '$2' ]] && echo "$0"'
find $1 -type d -exec sh -c "$COMMAND" {} \;

因此,您的脚本存在一些错误:

  1. 您正在使用mindepth而不是maxdepth
  2. egrep 中的句点 (.) 可以匹配任何字符。因此.wma可以匹配“Snowman.txt”。
  3. 您不需要对类型“d”进行第二次测试,因为只有目录被传递到 shell 命令中。

我的脚本注释:

  1. 用法是:findodd.sh <top_folder> <no_of_files>
  2. 引号很重要。 的定义COMMAND实际上是 两边各两个字符串文字$2。这真的很重要。
  3. 它仅列出包含文件的文件夹,而不是文件本身。要执行后者,您必须将 替换echo "$0"为另一个find

现在我已经在 Arch Linux 机器上进行测试,我的 shell 是“bash”,所以我不知道这是否可以在 OSX 上运行,因为并非所有 shell 都是平等的。:-)


早期的第一次尝试:

嗯。我不知道 OSX 和 Unix/Linux 有多相似,但我会尝试一下。

我相信,您这两个问题的答案都在于“sh -c”命令的第一个测试。这是以下内容:

$(find "$0" -mindepth 1 | wc -l) -eq 1

要将第二个参数传递给脚本以表示文件数量,您应该能够将“1”更改为 $2,因此测试将是:

$(find "$0" -mindepth 1 | wc -l) -eq $2

不要在两边加上引号$2,否则它将被解释为传递给“sh -c”命令的第二个参数,而不是您的脚本。

命令行将是:

./findodd.sh /Users/paul/Music 2

据我了解,为了满足您的第二个要求,您需要将命令放入egrep第一个测试中,因此:

$(find "$0" -mindepth 1 |egrep ".mp4|.mp3|.ogg|.flac|.wma|.m4a"| wc -l) -eq $2

不过你可能得看一下引文。

无论如何,尝试一下并告诉我们。

答案3

你可以在 Python 中通过如下方式实现这一点:

#!/usr/bin/env python

import fnmatch
import os
import sys

if len(sys.argv) != 3 or \
        not sys.argv[1].isdigit() or \
        not os.path.exists(sys.argv[2]):
    print "Usage: %s [number of files] [search root]" % sys.argv[0]
    sys.exit(1)

num_files = int(sys.argv[1])
search_root = sys.argv[2]

# this must be a tuple to work with endswith()
audio_extensions = (
    'mp4',
    'mp3',
    'ogg',
    'flac',
    'wma',
    'm4a',
)

for dirpath, dirnames, filenames in os.walk(search_root):
    audio_files = [f for f in filenames if f.endswith(audio_extensions)]
    if len(audio_files) == num_files:
        print "\n".join([os.path.join(dirpath, f) for f in audio_files])

如果您chmod +x findodd.py可以按照与运行当前脚本相同的方式运行它,例如:

./findodd.py 1 /Users/paul/Music

相关内容