查找不包含文件的目录

Oli*_*Oli 72 bash find

是的,我正在整理我的音乐。我已经按照以下口头禅将所有内容都安排得很好:/Artist/Album/Track - Artist - Title.ext如果存在,则封面位于/Artist/Album/cover.(jpg|png).

我想扫描所有的二级目录,找到那些没有封面的。第二级,我的意思是我不在乎/Britney Spears/没有cover.jpg,但我会在乎如果/Britney Spears/In The Zone/没有。

不要担心封面下载(明天对我来说这是一个有趣的项目)我只关心关于一个反向find例子的辉煌 bash-fuiness 。

pho*_*bos 94

案例 1:您知道要查找的确切文件名

使用findwithtest -e your_file检查文件是否存在。例如,您查找其中没有的目录cover.jpg

find base_dir -mindepth 2 -maxdepth 2 -type d '!' -exec test -e "{}/cover.jpg" ';' -print
Run Code Online (Sandbox Code Playgroud)

虽然它区分大小写。

案例 2:你想要更灵活

您不确定这种情况,扩展名可能是jPgpng...

find base_dir -mindepth 2 -maxdepth 2 -type d '!' -exec sh -c 'ls -1 "{}"|egrep -i -q "^cover\.(jpg|png)$"' ';' -print
Run Code Online (Sandbox Code Playgroud)

解释:

  • 您需要sh为每个目录生成一个 shell ,因为在使用时不可能进行管道传输find
  • ls -1 "{}"只输出find当前正在遍历的目录的文件名
  • egrep( 而不是grep) 使用扩展的正则表达式;-i使搜索不区分大小写,-q使其省略任何输出
  • "^cover\.(jpg|png)$"是搜索模式。在这个例子中,它匹配 eg cOver.png, Cover.JPGor cover.png。在.必须以其他方式逃脱它意味着它匹配任何字符。^标记行的开始,$结束

egrep 的其他搜索模式示例

egrep -i -q "^cover\.(jpg|png)$"部分替换为:

  • egrep -i -q "cover\.(jpg|png)$": 也匹配cd_cover.png, album_cover.JPG...
  • egrep -q "^cover\.(jpg|png)$": 匹配cover.png, cover.jpg, 但 NOT Cover.jpg(不关闭区分大小写)
  • egrep -iq "^(cover|front)\.jpg$": 匹配 eg front.jpg,Cover.JPG匹配 Cover.PNG

有关这方面的更多信息,请查看正则表达式


Oli*_*Oli 13

很简单,它发生了。下面获取带有封面的目录列表,并将其与所有二级目录的列表进行比较。出现在两个“文件”中的行被抑制,留下需要覆盖的目录列表。

comm -3 \
    <(find ~/Music/ -iname 'cover.*' -printf '%h\n' | sort -u) \
    <(find ~/Music/ -maxdepth 2 -mindepth 2 -type d | sort) \
| sed 's/^.*Music\///'
Run Code Online (Sandbox Code Playgroud)

万岁。

笔记:

  • comm的论据如下:

    • -1 抑制文件 1 独有的行
    • -2 抑制文件 2 独有的行
    • -3 抑制出现在两个文件中的行
  • comm只需要文件,因此古怪的<(...)输入法。这通过真正的 [临时] 文件来管理内容。

  • comm需要排序的输入,否则它不起作用并且find绝不保证订单。它也需要是独一无二的。第一个find操作可能会找到多个文件,cover.*因此可能会有重复的条目。sort -u迅速将这些归为一。第二个发现总是独一无二的。

  • dirname是一个方便的工具,无需求助于sed(et al)即可获取文件的目录。

  • find并且comm它们的输出都有些混乱。最后sed是清理东西,所以你只剩下Artist/Album. 这对您来说可能是也可能不是。

  • 你的第一个 `find` 可以简化为 `find ~/Music/ -iname 'cover.*' -printf '%h\n'`,避免使用 `dirname`。尽管 `dirname` 在其他地方很方便。 (2认同)

小智 9

使用 globbing 解决这个问题比使用 find 要好得多。

$ cd ... # to the directory one level above the album/artist structure

$ echo */*/*.cover   # lists all the covers

$ printf "%s\n" */*/*.cover # lists all the covers, one per line
Run Code Online (Sandbox Code Playgroud)

现在假设您在这个漂亮的结构中没有杂散文件。当前目录只包含艺术家子目录,那些只包含专辑子目录。然后我们可以做这样的事情:

$ diff  <(for x in */*/cover.jpg; do echo "$(dirname "$x")" ; done) <(printf "%s\n" */*)
Run Code Online (Sandbox Code Playgroud)

<(...)语法是bash进程替换:它可以让你在一个地方文件参数的使用的命令。它允许您将命令的输出视为文件。所以我们可以运行两个程序,并获取它们的差异,而无需将它们的输出保存在临时文件中。该diff程序认为它正在处理两个文件,但实际上它正在从两个管道中读取。

产生右手输入命令diffprintf "%s\n" */*,只是列出了专辑目录。左边的命令遍历*.cover路径并打印它们的目录名称。

测试运行:

$ find .   # let's see what we have here
.
./a
./a/b
./foo
./foo/bar
./foo/baz
./foo/baz/cover.jpg

$ diff  <(for x in */*/cover.jpg; do echo "$(dirname "$x")" ; done) <(printf "%s\n" */*)
0a1,2
> a/b
> foo/bar
Run Code Online (Sandbox Code Playgroud)

啊哈,a/bfoo/bar目录没有cover.jpg.

有一些破碎的角落情况,例如默认情况下,*如果它不匹配任何内容,则会扩展到自身。这可以通过 Bash 的set -o nullglob.