Encontre apenas as pastas que contêm um arquivo com o mesmo nome da pasta

8

Quero encontrar todas as subpastas, que contenham um arquivo de remarcação com o mesmo nome (e extensão .md).

Por exemplo: Desejo encontrar as seguintes subpastas:

Apple/Banana/Orange      #Apple/Banana/Orange/Orange.md exists
Apple/Banana             #Apple/Banana/Banana.md exists
Apple/Banana/Papaya      #Apple/Banana/Papaya/Papaya.md exists
  • Nota: Pode haver outros arquivos ou subdiretórios no diretório

Alguma sugestão?


As soluções para o problema podem ser testadas usando o seguinte código:

#!/usr/bin/env bash
# - goal: "Test"
# - author: Nikhil Agarwal
# - date: Wednesday, August 07, 2019
# - status: P T' (P: Prototyping, T: Tested)
# - usage: ./Test.sh
# - include:
#   1.
# - refer:
#   1. [directory - Find only those folders that contain a File with the same name as the Folder - Unix & Linux Stack Exchange](/unix/534190/find-only-those-folders-that-contain-a-file-with-the-same-name-as-the-folder)
# - formatting:
#   shellcheck disable=
#clear

main() {
    TestData
    ExpectedOutput
    TestFunction "${1:?"Please enter a test number, as the first argument, to be executed!"}"
}

TestFunction() {
    echo "Test Function"
    echo "============="
    "Test${1}"
    echo ""
}

Test1() {
    echo "Description: Thor"
    find . -type f -regextype egrep -regex '.*/([^/]+)/\1\.md$' | sort
    echo "Observation: ${Green:=}Pass, but shows filepath instead of directory path${Normal:=}"
}

Test2() {
    echo "Description: Kusalananda1"
    find . -type d -exec sh -c '
    dirpath=$1
    set -- "$dirpath"/*.md
    [ -f "$dirpath/${dirpath##*/}.md" ] && [ "$#" -eq 1 ]' sh {} \; -print | sort
    echo "Observation: ${Red:=}Fails as it ignores B.md${Normal:=}"
}

Test3() {
    echo "Description: Kusalananda2"
    find . -type d -exec sh -c '
    for dirpath do
        set -- "$dirpath"/*.md
        if [ -f "$dirpath/${dirpath##*/}.md" ] && [ "$#" -eq 1 ]
        then
            printf "%s\n" "$dirpath"
        fi
    done' sh {} + | sort
    echo "Observation: ${Red:=}Fails as it ignores B.md${Normal:=}"
}

Test4() {
    echo "Description: steeldriver1"
    find . -type d -exec sh -c '[ -f "$1/${1##*/}.md" ]' find-sh {} \; -print | sort
    echo "Observation: ${Green:=}Pass${Normal:=}"
}

Test5() {
    echo "Description: steeldriver2"
    find . -type d -exec sh -c '
  for d do
    [ -f "$d/${d##*/}.md" ] && printf "%s\n" "$d"
  done' find-sh {} + | sort
    echo "Observation: ${Green:=}Pass${Normal:=}"
}

Test6() {
    echo "Description: Stéphane Chazelas"
    find . -name '*.md' -print0 \
        | gawk -v RS='\0' -F/ -v OFS=/ '
    {filename = $NF; NF--
     if ($(NF)".md" == filename) include[$0]
     else exclude[$0]
    }
    END {for (i in include) if (!(i in exclude)) print i}'
    echo "Observation: ${Red:=}Fails as it ignores B.md${Normal:=}"
}

Test7() {
    echo "Description: Zach"
    #shellcheck disable=2044
    for fd in $(find . -type d); do
        dir=${fd##*/}
        if [ -f "${fd}/${dir}.md" ]; then
            ls "${fd}/${dir}.md"
        fi
    done
    echo "Observation: ${Green:=}Pass but shows filepath instead of directory${Normal:=}"
}
ExpectedOutput() {
    echo "Expected Output"
    echo "==============="
    cat << EOT
./GeneratedTest/A
./GeneratedTest/A/AA
./GeneratedTest/B
./GeneratedTest/C/CC1
./GeneratedTest/C/CC2
EOT
}

TestData() {
    rm -rf GeneratedTest

    mkdir -p GeneratedTest/A/AA
    touch GeneratedTest/index.md
    touch GeneratedTest/A/A.md
    touch GeneratedTest/A/AA/AA.md

    mkdir -p GeneratedTest/B
    touch GeneratedTest/B/B.md
    touch GeneratedTest/B/index.md

    mkdir -p GeneratedTest/C/CC1
    touch GeneratedTest/C/index.md
    touch GeneratedTest/C/CC1/CC1.md

    mkdir -p GeneratedTest/C/CC2
    touch GeneratedTest/C/CC2/CC2.md

    mkdir -p GeneratedTest/C/CC3
    touch GeneratedTest/C/CC3/CC.md

    mkdir -p GeneratedTest/C/CC4
}
main "$@"
Nikhil
fonte
11
Em relação às suas considerações finais. Observe que algumas respostas fazem coisas diferentes das outras. Mine and Stéphane de, por exemplo, interpretou o seu primeiro "nota" como "se existem outros arquivos de remarcação no directório qualquer , não retorne esse diretório", enquanto os outros não (tanto quanto eu posso ver). Além disso, somente você pode escolher a resposta que é mais útil para você . As respostas aqui continuarão recebendo votos positivos e negativos depois que você aceitar uma resposta, dependendo do que os outros leitores acharem mais útil.
Kusalananda
Quando você diz "Pastas que contêm arquivos de remarcação cujos nomes são diferentes não devem ser encontradas", você deseja excluir os diretórios com os dois? Por exemplo, se você tem foo/foo.mde foo/bar.mddeve fooser incluído ou excluído?
Kevin
@ Kevin No exemplo que você deu, eu pretendia incluir foo. Infelizmente, porém, muitas pessoas interpretaram de outra maneira e justificaram isso. Então, pensei que não estava claro na comunicação. Então, aceitei uma resposta que não incluía foo.
Nikhil
Se você usar -printfcom find, você pode obter qualquer parte do jogo você quer, ver a minha edição
Thor

Respostas:

13

Supondo que seus arquivos tenham nomes sensatos, ou seja, não há necessidade -print0etc. Você pode fazer isso com o GNU, assim:

find . -type f -regextype egrep -regex '.*/([^/]+)/\1\.md$'

Resultado:

./Apple/Banana/Orange/Orange.md
./Apple/Banana/Papaya/Papaya.md
./Apple/Banana/Banana.md

Se você deseja apenas o nome do diretório, adicione um -printfargumento:

find . -type f -regextype egrep -regex '.*/([^/]+)/\1\.md$' -printf '%h\n'

Saída quando executado nos dados de teste atualizados:

GeneratedTest/A/AA
GeneratedTest/A
GeneratedTest/C/CC2
GeneratedTest/C/CC1
GeneratedTest/B
Thor
fonte
Mesmo sem encontrar o GNU:find . -type f | egrep '.*/([^/]+)/\1\.md$'
Jim L.
3
@JimL. Exceto que canalizá-lo para uma ferramenta orientada a linhas quebraria alguns caracteres nos nomes de arquivos, como nova linha.
Kusalananda
11
@Kusalananda Concordou, no entanto, que essa resposta específica se baseia em arquivos com "nome sensato" que não exigem print0.
Jim L.
@Thor %hem printf é usado para o tipo int de dados a serem formatados. Referência: string de formato printf - Wikipedia . Poderia explicar essa parte? Como está %hsendo usado aqui?
22419 Nikhil
@ Nikik: Não find, consulte a seção 3.2.2.1 no manual para obter mais detalhes.
Thor
6

Em um sistema GNU, você pode fazer algo como:

find . -name '*.md' -print0 |
  gawk -v RS='\0' -F/ -v OFS=/ '
    {filename = $NF; NF--
     if ($(NF)".md" == filename) include[$0]
     else exclude[$0]
    }
    END {for (i in include) if (!(i in exclude)) print i}'
Stéphane Chazelas
fonte
3
você se importaria de incluir novamente sua solução zsh proposta como alternativa? seria útil para aqueles de nós tentando aprender mais sobre zsh
steeldriver
Dado que esta resposta recebeu mais votos: Para aqueles que estão votando positivamente nesta resposta, você poderia especificar por que isso é melhor que o resto? Ajudaria-me a escolher a resposta mais adequada.
Nikhil
Stéphane, eu concordo com a chave de aço. Mencione a zshsolução anterior (acredito que tenha duas das votações anteriores) e sinta-se à vontade para apontar quaisquer falhas nela que possam ter solicitado que você a remova.
Kusalananda
11
@steeldriver, nessa abordagem zsh, eu (como você) havia perdido a parte do requisito de que diretórios que contêm outros arquivos md devem ser omitidos.
Stéphane Chazelas 6/08/19
O @ StéphaneChazelas OP acabou de esclarecer nos comentários que ele realmente quis dizer para que aqueles fossem incluídos, era apenas fraseado e as pessoas entenderam isso literalmente.
Kevin Kevin
6
find . -type d -exec sh -c '
    dirpath=$1
    set -- "$dirpath"/*.md
    [ -f "$dirpath/${dirpath##*/}.md" ] && [ "$#" -eq 1 ]' sh {} \; -print

O exemplo acima encontraria todos os diretórios abaixo do diretório atual (incluindo o diretório atual) e executaria um script de shell curto para cada um.

O código do shell testaria se há um arquivo de remarcação com o mesmo nome que o diretório dentro do diretório e se esse é o único *.mdnome nesse diretório. Se esse arquivo existir e se for o único *.mdnome, o script de shell embutido sairá com um status de saída zero. Caso contrário, ele sai com um status de saída diferente de zero (falha na sinalização).

O set -- "$dirpath"/*.mdbit definirá os parâmetros posicionais para a lista de nomes de caminhos que correspondem ao padrão (corresponde a qualquer nome com um sufixo .mdno diretório). Podemos usar $#mais tarde para ver quantas correspondências obtivemos disso.

Se o script shell sair com sucesso, -printimprimirá o caminho para o diretório encontrado.

Versão ligeiramente mais rápida que usa menos invocações do script embutido, mas isso não permite que você faça mais com os nomes de caminho encontrados find(o script embutido pode ser expandido ainda mais):

find . -type d -exec sh -c '
    for dirpath do
        set -- "$dirpath"/*.md
        [ -f "$dirpath/${dirpath##*/}.md" ] &&
        [ "$#" -eq 1 ] &&
        printf "%s\n" "$dirpath"
    done' sh {} +

Os mesmos comandos, mas sem se preocupar se há outros .mdarquivos nos diretórios:

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

Veja também:

Kusalananda
fonte
4

Ou

find . -type d -exec sh -c '[ -f "$1/${1##*/}.md" ]' find-sh {} \; -print

ou

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

Para evitar a execução de um shpor arquivo.

O find-shé uma string arbitrária que se torna o parâmetro zeroth de posição do shell $0- tornando-o algo memorável pode ajudar na depuração caso o shell encontre erros (outros podem sugerir o uso de parâmetros simples shou mesmo _como um parâmetro "skip" padrão).

chave de aço
fonte
0

Aqui está o meu. Eu adicionei mais alguns diretórios e arquivos para verificar. Eu também estava entediado, então adicionei o horário da última modificação e o MD5. Talvez você esteja procurando duplicatas.

GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m'

mkdir -pv {Pear,Grape,Raisin,Plaintain}/{DragonFruit,Nababa,Strawberry,Grape,Raisin}
touch {Pear,Grape,Raisin,Plaintain}/{DragonFruit,Nababa,Strawberry,Grape,Raisin}/{Strawberry,Grape,Raisin}.md

for dir in $(find ./ -type d)
do
    dirname="${dir##*/}"
    fname="${dirname}.md"
    if [ -f "${dir}/${fname}" ]
    then
        STAT=$(stat --printf="%y %s" "${dir}/${fname}")
        STAT="${STAT:0:19}"
        MD5=$(md5sum "${dir}/${fname}")
        MD5="${MD5:0:32}"
        printf "${GREEN}%-60s${NC}%-40s%-40s\n" "'${dir}/${fname}' exists" "$STAT" "$MD5"
    else
        echo -e "${RED}'${dir}/${fname}' doesn't exist${NC}"
    fi
done

'.//.md' doesn't exist
'./Raisin/Raisin.md' doesn't exist
'./Raisin/Raisin/Raisin.md' exists                          2019-08-07 19:54:09      a3085274bf23c52c58dd063faba0c36a
'./Raisin/Nababa/Nababa.md' doesn't exist
'./Raisin/Strawberry/Strawberry.md' exists                  2019-08-07 19:54:09      3d2eca1d4a3c539527cb956affa8b807
'./Raisin/Grape/Grape.md' exists                            2019-08-07 19:54:09      f577b20f93a51286423c1d8973973f01
'./Raisin/DragonFruit/DragonFruit.md' doesn't exist
'./Pear/Pear.md' doesn't exist
'./Pear/Raisin/Raisin.md' exists                            2019-08-07 19:54:09      61387f5d87f125923c2962b389b0dd67
'./Pear/Nababa/Nababa.md' doesn't exist
'./Pear/Strawberry/Strawberry.md' exists                    2019-08-07 19:54:09      02c9e39ba5b77954082a61236f786d34
'./Pear/Grape/Grape.md' exists                              2019-08-07 19:54:09      43e85d5651cac069bba8ba36e754079d
'./Pear/DragonFruit/DragonFruit.md' doesn't exist
'./Apple/Apple.md' doesn't exist
'./Apple/Banana/Banana.md' exists                           2019-08-07 19:54:09      a605268f3314411ec360d7e0dd234960
'./Apple/Banana/Papaya/Papaya.md' exists                    2019-08-07 19:54:09      e759a879942fe986397e52b7ba21a9ff
'./Apple/Banana/Orange/Orange.md' exists                    2019-08-07 19:54:09      127618fe9ab73937836b809fa0593572
'./Plaintain/Plaintain.md' doesn't exist
'./Plaintain/Raisin/Raisin.md' exists                       2019-08-07 19:54:09      13ed6460f658ca9f7d222ad3d07212a2
'./Plaintain/Nababa/Nababa.md' doesn't exist
'./Plaintain/Strawberry/Strawberry.md' exists               2019-08-07 19:54:09      721d7a5a32f3eacf4b199b74d78b91f0
'./Plaintain/Grape/Grape.md' exists                         2019-08-07 19:54:09      0bdaff592bbd9e2ed5fac5a992bb3566
'./Plaintain/DragonFruit/DragonFruit.md' doesn't exist
'./Grape/Grape.md' doesn't exist
'./Grape/Raisin/Raisin.md' exists                           2019-08-07 19:54:09      aa5d4c970e7b4b6dc35cd16d1863b5bb
'./Grape/Nababa/Nababa.md' doesn't exist
'./Grape/Strawberry/Strawberry.md' exists                   2019-08-07 19:54:09      8b02f8273bbff1bb3162cb088813e0c9
'./Grape/Grape/Grape.md' exists                             2019-08-07 19:54:09      5593d7d6fdcbb48ab5901ba30469bbe8
user208145
fonte
-1

Isso exigiria um pouco de lógica.

for fd in `find . -type d`; do
  dir=${fd##*/}
  if [ -f ${fd}/${dir}.md ]; then
    ls ${fd}/${dir}.md
  fi
done

Você também pode adaptar isso para caber em um liner usando blocos de código.

EDIT: Bash é difícil. basedirnão é um comando, dirnamenão faz o que eu pensava, então vamos com a expansão de parâmetros.

Zach Sanchez
fonte
Isso seria porque aparentemente não consigo me lembrar dos comandos do bash ou de como eles funcionam.
Zach Sanchez
dirnameé o comando que você está procurando e as atribuições não podem ter espaços em torno do =.
Kusalananda
Descobrimos isso rapidamente, depois de apontado, e os espaços eram um erro de digitação.
Zach Sanchez
Isso quebra em todos os tipos de nomes de arquivos, especialmente em espaços. Não analise a saída de ls ou encontre . Veja as outras respostas aqui para abordagens sensatas.
Gilles 'SO- stop be evil'
Ah, droga, certo, eu pensaria que o loop for enumeraria por nova linha, não por espaço em branco arbitrário. Eu quebro essa regra o tempo todo porque raramente encontro arquivos ou diretórios com espaços, o que é ruim.
Zach Sanchez