查找不包含文件的目录


58

是的,我正在整理音乐。我按照下面的口头禅安排了所有东西:/Artist/Album/Track - Artist - Title.ext如果有的话,把它放进去/Artist/Album/cover.(jpg|png)

我想浏览所有第二级目录并找到没有封面的目录。在第二层,我的意思是我不在乎是否/Britney Spears/没有cover.jpg,但我不在乎是否/Britney Spears/In The Zone/没有Cover.jpg 。

不用担心封面下载(明天对我来说是个有趣的项目),我只关心一个反面find例子的光荣bash-fuiness 。


对于有兴趣下载缺少的封面的任何人,只需安装launchpad.net/coverlovin并将@phoibos答案中的-print替换为“ -exec ./coverlovin.py {} \;”。
Dror Cohen 2014年

Answers:


81

情况1:您知道要查找的确切文件名

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

find base_dir -mindepth 2 -maxdepth 2 -type d '!' -exec test -e "{}/cover.jpg" ';' -print

不过是区分大小写的。

情况2:您想要更灵活

您不确定情况如何,扩展名可能是jPgpng...

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

说明:

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

egrep的其他搜索模式示例

用以下egrep -i -q "^cover\.(jpg|png)$"部分代替:

  • egrep -i -q "cover\.(jpg|png)$":也匹配cd_cover.pngalbum_cover.JPG...
  • egrep -q "^cover\.(jpg|png)$":匹配cover.pngcover.jpg但不匹配Cover.jpg(不关闭区分大小写)
  • egrep -iq "^(cover|front)\.jpg$":匹配例如front.jpgCover.JPG匹配 Cover.PNG

有关更多信息,请查看正则表达式


绝对漂亮-问题在于在案例或不同扩展名之间选择不灵活(我尝试使用通配符,但不可以)。我想知道是否还有更好的选择test
奥利(Oli)

1
嗯,您可以将其与此嵌套在一起,-exec bash -c '[[ -n $(find "{}" -iname "cover.*") ]]' \;但是就优化而言,这是相当肮脏的。它确实可以工作。
奥利(Oli)

我发现可以传递OR查询test的负载-o EXPRESSION...例如:test -e "{}/cover.jpg" -o -e "{}/cover.png"比进行完整的搜索要好,但仍然区分大小写。
奥利(Oli)

我应该注意到,将这种性能(两项测试,我最后的评论)与其他两种解决方案(comm'd find和comm'd globing)进行比较,这是迄今为止最慢的(分别为684ms与40ms和50ms)
Oli

原始的in-answer解决方案要花一秒钟的时间,并且会破坏$目录名称(例如Ke $ ha)中的情况。
奥利(Oli)

12

很简单,很有趣。以下内容带有封面,并与所有第二级目录的列表进行比较。两个“文件”中出现的行均被删除,剩下需要覆盖的目录列表。

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

万岁。

笔记:

  • comm的参数如下:

    • -1 取消file1特有的行
    • -2 取消file2特有的行
    • -3 禁止出现在两个文件中的行
  • comm只接受文件,因此是怪异的<(...)输入法。这将通过真实的[临时]文件传输内容。

  • comm需要排序的输入,否则将不起作用,find也绝不能保证订单。它也必须是唯一的。第一个find操作可以找到多个文件,cover.*因此可能有重复的条目。sort -u很快将那些皱成一团。第二个发现总是独一无二的。

  • dirname是一种无需借助即可获取文件目录的便捷工具sed

  • find并且comm它们的输出都有些混乱。最后的地方sed是清理东西,所以您就可以选择了Artist/Album。这可能对您不希望如此。


2
您的第一个find可能会简化为find ~/Music/ -iname 'cover.*' -printf '%h\n',避免了需要dirname。虽然dirname在其他地方很方便。
汤姆(Tom)

感谢@汤姆,这是一个很多快于分叉地方(我的音乐目录29ms VS 734ms -无论是“温暖”的认定)
奥利

9

使用glob来解决比使用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

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

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

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

产生到diff,的右侧输入的命令printf "%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

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

有一些破损的情况,例如,*如果不匹配,默认情况下会扩展为自身。这可以用Bash的解决set -o nullglob


抱歉回复晚。这是一个有趣的想法,但是:Covers可以是png和jpb格式,不会commdiff吗?
奥利(Oli)

comm -3 <(printf "%s\n" */*/cover* | sed -r 's/\/[^\/]+$//' | sort -u) <(printf "%s\n" */*)似乎没有任何diff起毛的妥协。但是,它比我的双重发现要慢一些。
奥利(Oli)

0
ls --color=never */*.txt | sed 's|/.*||' | sort -u -n > withtxt.txt
ls --color=never -d * | sort -u -n > all.txt
diff all.txt withtxt.txt

将显示所有没有txt文件的目录。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.