Encontre diretórios que NÃO contêm um arquivo


58

Sim, estou classificando minha música. Eu tenho tudo organizado lindamente no seguinte mantra: /Artist/Album/Track - Artist - Title.exte, se houver, a capa fica /Artist/Album/cover.(jpg|png).

Quero examinar todos os diretórios de segundo nível e encontrar os que não têm capa. No segundo nível, quero dizer que não me importo se /Britney Spears/não tiver um cover.jpg, mas me importaria se /Britney Spears/In The Zone/não tivesse um.

Não se preocupe com o download da capa (esse é um projeto divertido para mim amanhã). Só me preocupo com o glorioso entusiasmo por um findexemplo inverso .


para quem estiver interessado em fazer o download das capas ausentes, basta instalar o launchpad.net/coverlovin e substituir a -print na resposta @phoibos por "-exec ./coverlovin.py {} \;"
— Dror Cohen

Respostas:


81

Caso 1: você sabe o nome exato do arquivo a procurar

Use findcom test -e your_filepara verificar se existe um arquivo. Por exemplo, você procura por diretórios que não contêm cover.jpgneles:

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

É sensível a maiúsculas.

Caso 2: você quer ser mais flexível

Você não tem certeza do caso, e a extensão pode ser jPg, png...

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

Explicação:

  • Você precisa gerar um shell shpara cada diretório, pois a tubulação não é possível ao usarfind
  • ls -1 "{}"gera apenas os nomes dos arquivos do diretório que findestá atravessando
  • egrep(em vez de grep) usa expressões regulares estendidas; -ifaz com que o caso de pesquisa seja insensível, -qomite qualquer saída
  • "^cover\.(jpg|png)$"é o padrão de pesquisa. Neste exemplo, ele corresponde cOver.png, por exemplo , Cover.JPGou cover.png. O .deve ser escapado, caso contrário, significa que ele corresponde a qualquer caractere. ^marca o início da linha, $seu fim

Outros exemplos de padrões de pesquisa para egrep :

Substitua a egrep -i -q "^cover\.(jpg|png)$"peça por:

  • egrep -i -q "cover\.(jpg|png)$": Também corresponde cd_cover.png, album_cover.JPG...
  • egrep -q "^cover\.(jpg|png)$": Corresponde cover.png, cover.jpgmas NÃO Cover.jpg(a distinção entre maiúsculas e minúsculas não está desativada)
  • egrep -iq "^(cover|front)\.jpg$": corresponde front.jpg, por exemplo , Cover.JPGmas não Cover.PNG

Para mais informações, consulte Expressões regulares .


Absolutamente bonito - com o problema de não ser flexível escolher entre casos ou extensões diferentes (tentei um curinga, mas não utilizei). Gostaria de saber se existe uma alternativa melhor para test.
— Oli

11
Hmm, você pode aninhar o achado com isso, -exec bash -c '[[ -n $(find "{}" -iname "cover.*") ]]' \;mas isso é bastante imundo em termos de otimização. Mas funciona.
— Oli

Descobri que você pode passar testuma carga de -o EXPRESSIONconsultas OR ... por exemplo: o test -e "{}/cover.jpg" -o -e "{}/cover.png"que é melhor do que fazer uma pesquisa completa, mas ainda diferencia maiúsculas de minúsculas.
— Oli

Devo observar que comparar o desempenho disso (dois testes, pelo meu último comentário) com as outras duas soluções (com e sem contato) e este é de longe o mais lento (684ms vs 40ms e 50ms respectivamente)
— Oli

A solução original de resposta leva mais de um segundo e quebra nas circunstâncias que possuem $o nome do diretório (Ke $ ha, por exemplo).
— Oli

12

Simples, transparece. A seguir, obtém uma lista de diretórios com a capa e a compara com uma lista de todos os diretórios de segundo nível. As linhas que aparecem nos dois "arquivos" são suprimidas, deixando uma lista de diretórios que precisam de cobertura.

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

Viva.

Notas:

  • commOs argumentos de são os seguintes:

    • -1 suprimir linhas exclusivas para file1
    • -2 suprimir linhas exclusivas para file2
    • -3 suprimir linhas que aparecem nos dois arquivos
  • commleva apenas arquivos, daí o <(...)método de entrada excêntrico . Isso canaliza o conteúdo através de um arquivo [temporário] real.

  • commprecisa de entrada classificada ou não funciona e findde forma alguma garante um pedido. Ele também precisa ser único. A primeira findoperação pode encontrar vários arquivos para cover.*que possa haver entradas duplicadas. sort -urapidamente agrupa esses números em um. A segunda descoberta sempre será única.

  • dirnameé uma ferramenta útil para obter o diretório de um arquivo sem recorrer a sed(et al).

  • finde commsão ambos um pouco bagunçados com sua saída. A final sedestá lá para limpar as coisas, para que você fique com ele Artist/Album. Isso pode ou não ser desejável para você.


2
seu primeiro findpode ser simplificado find ~/Music/ -iname 'cover.*' -printf '%h\n', evitando a necessidade dirname. embora dirnameseja útil em outro lugar.
— Tom

Obrigado @ Tom, que é muito mais rápido do que bifurcar-se em todos os lugares (29ms vs 734ms no meu dir de música - ambos achados "quentes")
— Oli

9

É muito melhor resolver com globbing do que com 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

Agora, suponha que você não tenha arquivos perdidos nessa bela estrutura. O diretório atual contém apenas subdiretórios de artistas e esses contêm apenas subdiretórios de álbuns. Então podemos fazer algo assim:

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

A <(...)sintaxe é a substituição do processo Bash: permite usar um comando no lugar de um argumento de arquivo. Permite tratar a saída de um comando como um arquivo. Portanto, podemos executar dois programas e obter as diferenças deles, sem salvar sua saída em arquivos temporários. O diffprograma pensa que está trabalhando com dois arquivos, mas na verdade está lendo de dois canais.

O comando que produz a entrada de mão direita para diff, printf "%s\n" */*, apenas lista os diretórios do álbum. O comando à esquerda percorre os *.covercaminhos e imprime seus nomes de diretório.

Execução de teste:

$ 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

Aha, os diretórios a/be foo/barnão têm cover.jpg.

Existem alguns casos de esquina quebrados, como se por padrão se *expandisse para si mesmo se não corresponder a nada. Isso pode ser resolvido com o Bash's set -o nullglob.


Me desculpe pela resposta atrasada. É uma idéia interessante, mas: as capas podem estar em png e jpb e, não commseriam mais limpas do que diff?
— Oli

comm -3 <(printf "%s\n" */*/cover* | sed -r 's/\/[^\/]+$//' | sort -u) <(printf "%s\n" */*)parece um compromisso sensato, sem nenhum diffcotão. É, no entanto, um pouco mais lento que o meu duplo achado.
— 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

Irá mostrar todos os diretórios que não possuem arquivos txt.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.