Localizando todos os arquivos com uma determinada extensão cujo nome base é o nome do diretório pai

9

Quero procurar recursivamente todos os *.pdfarquivos em um diretório ~/foocujo nome base corresponda ao nome do diretório pai do arquivo.

Por exemplo, suponha que a estrutura de diretórios tenha ~/fooesta aparência

foo
├── dir1
│   ├── dir1.pdf
│   └── dir1.txt
├── dir2
│   ├── dir2.tex
│   └── spam
│       └── spam.pdf
└── dir3
    ├── dir3.pdf
    └── eggs
        └── eggs.pdf

Executar meu comando desejado retornaria

~/foo/dir1/dir1.pdf
~/foo/dir2/spam/spam.pdf
~/foo/dir3/dir3.pdf
~/foo/dir3/eggs/eggs.pdf

Isso é possível usando findou algum outro utilitário principal? Presumo que isso é possível usando a -regexopção para, findmas não tenho certeza de como escrever o padrão correto.

Brian Fitzpatrick
fonte
Sim, vou simular um exemplo agora.
Brian Fitzpatrick
1
@Inian Adicionado um exemplo. Isso ajuda?
Brian Fitzpatrick

Respostas:

16

Com o GNU find:

find . -regextype egrep -regex '.*/([^/]+)/\1\.pdf'
  • -regextype egrep use regex estilo egrep.
  • .*/ coincidir com diretórios dos principais pais.
  • ([^/]+)/ corresponde ao diretório pai em um grupo.
  • \1\.pdfuse backreferencepara combinar o nome do arquivo como o diretório pai.

atualizar

Um (eu mesmo) pode pensar que .*é ganancioso o suficiente, é desnecessário excluir /da correspondência dos pais:

find . -regextype egrep -regex '.*/(.+)/\1\.pdf'

O comando acima não funcionará bem, porque combina ./a/b/a/b.pdf:

  • .*/ fósforos ./
  • (.+)/ fósforos a/b/
  • \1.pdf fósforos a/b.pdf
dedowsdi
fonte
Muito legal. Gostaria de poder regex isso bem.
Brian Fitzpatrick
Ou find . -regex '.*/\([^/]*\)/\1\.pdf'então funcionaria com o BSD find.
Stéphane Chazelas
7

A variante de loop tradicional de find .. -exec sh -c ''usar as construções de shell para corresponder ao nome da base e o caminho imediato acima seria o seguinte.

find foo/ -name '*.pdf' -exec sh -c '
    for file; do 
        base="${file##*/}"
        path="${file%/*}"
        if [ "${path##*/}" =  "${base%.*}" ]; then
            printf "%s\n" "$file" 
        fi
    done' sh {} +

Para detalhar as expansões de parâmetros individuais

  • filecontém o caminho completo do .pdfarquivo retornado do findcomando
  • "${file##*/}"contém apenas a parte após a última, /ou seja, apenas o nome da base do arquivo
  • "${file%/*}"contém o caminho até a final, /ou seja, exceto a parte do nome da base do resultado
  • "${path##*/}"contém a parte após a última /da pathvariável, ou seja, o caminho imediato da pasta acima do nome da base do arquivo
  • "${base%.*}"contém a parte do nome da base com a .pdfextensão removida

Portanto, se o nome da base sem extensão corresponder ao nome da pasta imediata acima, imprimimos o caminho.

Inian
fonte
7

O inverso da resposta do Inian , ou seja, procure por diretórios e veja se eles contêm um arquivo com um nome específico.

A seguir, imprime os nomes dos caminhos dos arquivos encontrados em relação ao diretório foo:

find foo -type d -exec sh -c '
    for dirpath do
        pathname="$dirpath/${dirpath##*/}.pdf"
        if [ -f "$pathname" ]; then
            printf "%s\n" "$pathname"
        fi
    done' sh {} +

${dirpath##*/}será substituído pela parte do nome do arquivo do caminho do diretório e poderá ser substituído por $(basename "$dirpath").

Para pessoas que gostam da sintaxe de curto-circuito:

find foo -type d -exec sh -c '
    for dirpath do
        pathname="$dirpath/${dirpath##*/}.pdf"
        [ -f "$pathname" ] && printf "%s\n" "$pathname"
    done' sh {} +

O benefício de fazer dessa maneira é que você pode ter mais arquivos PDF do que diretórios. O número de testes envolvidos é reduzido se um restringir a consulta pelo número menor (o número de diretórios).

Por exemplo, se um único diretório contiver 100 arquivos PDF, isso tentaria detectar apenas um deles, em vez de testar os nomes de todos os 100 arquivos em relação ao diretório.

Kusalananda
fonte
3

com zsh:

printf '%s\n' **/*/*.pdf(e@'[[ $REPLY:t = $REPLY:h:t.pdf ]]'@)

Cuidado que, embora **/não siga os links simbólicos, */seguirá.

Stéphane Chazelas
fonte
2

Não foi especificado, mas aqui está uma solução sem expressões regulares, se alguém estiver interessado.

Podemos usar find . -type fapenas para obter arquivos, depois utilizar dirnamee basenameescrever o condicional. Os utilitários têm o seguinte comportamento:

$ find . -type f
./dir2/spam/spam.pdf
./dir2/dir2.tex
./dir3/dir3.pdf
./dir3/eggs/eggs.pdf
./dir1/dir1.pdf
./dir1/dir1.txt

basenameretorna apenas o nome do arquivo após o último /:

$ for file in $(find . -type f); do basename $file; done
spam.pdf
dir2.tex
dir3.pdf
eggs.pdf
dir1.pdf
dir1.txt

dirnamefornece todo o caminho até a final /:

$ for file in $(find . -type f); do dirname $file; done
./dir2/spam
./dir2
./dir3
./dir3/eggs
./dir1
./dir1

Portanto, basename $(dirname $file)fornece o diretório pai do arquivo.

$ for file in $(find . -type f); do basename $(dirname $file) ; done
spam
dir2
dir3
eggs
dir1
dir1

Solução

Combine o que foi dito acima para formar o condicional e "$(basename $file)" = "$(basename $(dirname $file))".pdf, em seguida, imprima cada resultado apenas findse esse condicional retornar verdadeiro.

$ while read file; do if [ "$(basename "$file")" = "$(basename "$(dirname "$file")")".pdf ]; then echo $file; fi done < <(find . -type f)
./dir2/spam/spam.pdf
./dir3/dir3.pdf
./dir3/eggs/eggs.pdf
./dir1/dir1.pdf
./Final Thesis/grits/grits.pdf
./Final Thesis/Final Thesis.pdf

No exemplo acima, adicionamos um diretório / arquivo com espaços no nome para tratar desse caso (graças a @Kusalananda nos comentários)

user1717828
fonte
Infelizmente, isso quebrará nomes de arquivos como Final Thesis.pdf(com um espaço).
Kusalananda
@Kusalananda Fixed.
User1717828 21/04/19
0

Faço bash globbing, testes simples de loop sobre string em qualquer dia no programa Find . Me chame de irracional e, embora possa ser subótimo, esse código simples serve para mim: legível e reutilizável, satisfazendo até! Permitam-me, portanto, sugerir uma combinação de:

• festa globstar : for f in ** ; do ... ** laços mais de cada arquivos no diretório atual e todas as subpastas .. para verificar o status globstar em sua sessão atual: shopt -p globstar. Para globstar ativar: shopt -s globstar.

• utlity "file" : if [[ $(file "$f") =~ pdf ]]; then ... para verificar o formato real do arquivo em pdf - mais robusto do que testar apenas a extensão do arquivo

• basename, dirname : para comparar o nome do arquivo com o nome do diretório imediatamente acima dele. basenameretorna o nome do arquivo - dirnameretorna o caminho completo do diretório - combine as duas funções para retornar apenas o diretório que contém o arquivo correspondente. Coloquei cada um em uma variável ( _mydir e _myf ) para fazer um teste simples usando = ~ para correspondência de string.

Uma subtilidade: remova qualquer "ponto" no nome do arquivo para evitar que ele corresponda ao diretório atual, cujo atalho também é "." - Usei a substituição direta de strings na variável _myf : ${_myf//./}- não é muito elegante, mas funciona. Partidas positivas irá retornar o caminho de cada arquivo - juntamente com o caminho completo da pasta atual precedendo a saída com: $(pwd)/.

Código

for f in ** ; do
  if [[ $(file "$f") =~ PDF ]]; then
    _mydir="$(basename $(dirname $f))" ; 
    _myf="$(basename $f)" ; 
    [[ "${_myf//./}" =~ "$_mydir" ]] && echo -e "$(pwd)/$f" ; 
  fi ; 
done
docgyneco69
fonte