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

47

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

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

Não se preocupe com o download da capa (isso é um projeto divertido para mim amanhã). Eu só me preocupo com a gloriosa explosão sobre um exemplo inverso-ish find .

    
por Oli 06.10.2012 / 01:27

3 respostas

11

Simples, isso transparece. O seguinte obtém uma lista de diretórios com a capa e compara isso com uma lista de todos os diretórios de segundo nível. Linhas que aparecem em ambos os "arquivos" são suprimidas, deixando uma lista de diretórios que precisam de capas.

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

Hooray.

Notas:

  • Os argumentos de

    comm são os seguintes:

    • -1 suprime linhas exclusivas do arquivo1
    • -2 suprime linhas exclusivas do arquivo2
    • -3 suprime as linhas que aparecem nos dois arquivos
  • comm só recebe arquivos, por isso o método de entrada <(...) kooky. Isso canaliza o conteúdo por meio de um arquivo real [temporário].

  • comm precisa de entrada classificada ou não funciona e find não garante de forma alguma um pedido. Também precisa ser único. A primeira operação find pode encontrar vários arquivos para cover.* , para que possa haver entradas duplicadas. sort -u rapidamente faz com que eles caiam 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).

  • find e comm estão um pouco confusos com sua saída. O sed final está lá para limpar as coisas, então você fica com Artist/Album . Isso pode ou não ser desejável para você.

por Oli 06.10.2012 / 01:39
63

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

Use find com test -e your_file para verificar se existe um arquivo. Por exemplo, você procura diretórios que não possuem cover.jpg :

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

É sensível a maiúsculas e minú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 sh para cada diretório, já que a canalização não é possível ao usar find
  • ls -1 "{}" exibe apenas os nomes de arquivos do diretório find que está sendo percorrido no momento
  • egrep (em vez de grep ) usa expressões regulares estendidas; -i torna o caso de pesquisa insensível, -q faz omitir qualquer saída
  • "^cover\.(jpg|png)$" é o padrão de pesquisa. Neste exemplo, corresponde a, e. cOver.png , Cover.JPG ou cover.png . O . deve ser de escape, caso contrário, significa que ele corresponde a qualquer caractere . ^ marca o início da linha, $ seu final

Outros exemplos de padrão de pesquisa para egrep :

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

  • egrep -i -q "cover\.(jpg|png)$" : também corresponde a cd_cover.png , album_cover.JPG ...
  • egrep -q "^cover\.(jpg|png)$" : corresponde a cover.png , cover.jpg , mas NÃO Cover.jpg (a diferenciação de maiúsculas e minúsculas não está desativada)
  • egrep -iq "^(cover|front)\.jpg$" : corresponde, por ex. front.jpg , Cover.JPG mas não Cover.PNG

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

    
por phoibos 06.10.2012 / 01:55
7

Isso é 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 estrutura legal. O diretório atual contém apenas subdiretórios do artista e esses contêm apenas subdiretórios do álbum. 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. Ele permite tratar a saída de um comando como um arquivo. Assim, podemos executar dois programas e obter seu diff, sem salvar sua saída em arquivos temporários. O programa diff acha que está trabalhando com dois arquivos, mas na verdade está lendo dois canais.

O comando que produz a entrada do lado direito para diff , printf "%s\n" */* , apenas lista os diretórios de álbuns. O comando do lado esquerdo percorre os caminhos *.cover 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/b e foo/bar não têm cover.jpg .

Existem alguns casos de canto quebrados, por exemplo * se expande para si mesmo se não corresponder a nada. Isso pode ser resolvido com set -o nullglob do Bash.

    
por Anon 06.10.2012 / 06:20

Tags