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 .

Oli
fonte
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 .

phoibos
fonte
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ê.

Oli
fonte
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.

Anon
fonte
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.

Roel Van de Paar
fonte