Rechercher uniquement les dossiers contenant un fichier portant le même nom que le dossier

8

Je veux trouver tous les sous-dossiers, qui contiennent un fichier de démarque avec le même nom (et l'extension .md).

Par exemple: je souhaite rechercher les sous-dossiers suivants:

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
  • Remarque: Il peut y avoir d'autres fichiers ou sous-répertoires dans le répertoire.

Aucune suggestion?


Les solutions au problème peuvent être testées à l'aide du code suivant:

#!/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
la source
1
Concernant vos remarques finales. Notez que certaines réponses font des choses différentes des autres. Des mines et de Stéphane par exemple, interprété votre première « note » que «s'il y a d' autres fichiers Markdown dans le répertoire que ce soit , ne retournez pas ce répertoire » tandis que les autres ne le font pas (pour autant que je peux voir). En dehors de cela, vous seul pouvez choisir la réponse qui vous est la plus utile . Les réponses ici continueront de recevoir des votes de haut en bas une fois que vous aurez accepté une réponse, selon ce que les autres lecteurs trouvent le plus utile.
Kusalananda
Lorsque vous dites que les dossiers contenant un fichier de démarque dont les noms sont différents ne doivent pas être trouvés, voulez-vous exclure les répertoires contenant les deux? Par exemple , si vous avez foo/foo.mdet foo/bar.mddoit fooêtre inclus ou exclus?
Kevin
@Kevin Dans l'exemple que vous avez donné, j'avais voulu inclure foo. Mais malheureusement, beaucoup de gens ont interprété dans l'autre sens et ils l'ont justifié. Donc, je pensais que je n'étais pas clair en communication. J'ai donc accepté une réponse qui n'incluait pas foo.
Nikhil
Si vous utilisez -printfavec find, vous pouvez obtenir n'importe quelle partie du match que vous voulez, voir mon montage
Thor

Réponses:

13

En supposant que vos fichiers portent un nom raisonnable, c'est-à-dire pas besoin de -print0etc. Vous pouvez le faire avec GNU find comme ceci:

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

Production:

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

Si vous ne voulez que le nom du répertoire, ajoutez un -printfargument:

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

Sortie lors de l'exécution sur vos données de test mises à jour:

GeneratedTest/A/AA
GeneratedTest/A
GeneratedTest/C/CC2
GeneratedTest/C/CC1
GeneratedTest/B
Thor
la source
Même sans GNU trouver:find . -type f | egrep '.*/([^/]+)/\1\.md$'
Jim L.
3
@JimL. Sauf que le rediriger vers un outil orienté ligne briserait certains caractères dans les noms de fichiers, comme la nouvelle ligne.
Kusalananda
1
@Kusalananda D'accord, cependant, cette réponse particulière est fondée sur des fichiers "judicieusement nommés" qui ne nécessitent pas print0.
Jim L.
@Thor %hdans printf est utilisé pour le type int de données à formater. Référence: chaîne de format printf - Wikipedia . Pourriez-vous expliquer cette partie? Comment est %hutilisé ici?
Nikhil
@Nikhil: Pas avec find, voir la section 3.2.2.1 du manuel pour plus de détails.
Thor
6

Sur un système GNU, vous pourriez faire quelque chose comme:

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
la source
3
Pourriez-vous ré-inclure votre solution zsh proposée comme alternative? il serait utile pour ceux d'entre nous d'essayer d'en savoir plus sur zsh
steeldriver
Étant donné que cette réponse a reçu plus de votes: Pour ceux qui votent pour cette réponse, pourriez-vous s'il vous plaît préciser pourquoi c'est mieux que les autres? Cela m'aiderait à choisir la réponse la plus appropriée.
Nikhil
Stéphane, je suis d'accord avec Steeldriver. Mentionnez la zshsolution précédente (elle a obtenu, je crois, deux des votes positifs) et n'hésitez pas à signaler les défauts qui pourraient vous avoir incité à la supprimer.
Kusalananda
1
@steeldriver, dans cette approche zsh, j'avais (comme vous) manqué la partie de l'exigence que les répertoires qui contiennent d'autres fichiers md soient omis.
Stéphane Chazelas
@ StéphaneChazelas OP vient de clarifier dans les commentaires qu'il voulait réellement dire pour ceux à inclure, il était juste mal formulé et les gens le prenaient trop à la lettre.
Kevin
6
find . -type d -exec sh -c '
    dirpath=$1
    set -- "$dirpath"/*.md
    [ -f "$dirpath/${dirpath##*/}.md" ] && [ "$#" -eq 1 ]' sh {} \; -print

Ce qui précède trouverait tous les répertoires sous le répertoire courant (y compris le répertoire courant) et exécuterait un court script shell pour chacun.

Le code shell testerait s'il y a un fichier de démarque avec le même nom que le répertoire dans le répertoire, et si c'est le seul *.mdnom dans ce répertoire. Si un tel fichier existe et s'il s'agit du seul *.mdnom, le script shell en ligne se termine avec un état de sortie nul. Sinon, il sort avec un état de sortie non nul (échec de signalisation).

Le set -- "$dirpath"/*.mdbit définira les paramètres de position sur la liste des chemins correspondant au modèle (correspond à n'importe quel nom avec un suffixe .mddans le répertoire). Nous pouvons ensuite utiliser $#plus tard pour voir combien de correspondances nous en avons tirées.

Si le script shell se termine avec succès, -printaffichera le chemin d'accès au répertoire trouvé.

Version légèrement plus rapide qui utilise moins d'invocations du script en ligne, mais qui ne vous permet pas d'en faire plus avec les chemins d'accès trouvés en findsoi (le script en ligne peut cependant être développé):

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

Les mêmes commandes mais sans se soucier s'il y a d'autres .mdfichiers dans les répertoires:

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 {} +

Voir également:

Kusalananda
la source
4

Soit

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 {} +

Pour éviter d'en exécuter un shpar fichier.

Il find-shs'agit d'une chaîne arbitraire qui devient le paramètre de position zéro du shell $0- en faire quelque chose de mémorable peut aider au débogage au cas où le shell rencontrerait des erreurs (d'autres peuvent suggérer d'utiliser plain shou même _comme paramètre par défaut "skip").

tournevis
la source
0

Voici la mienne. J'ai ajouté quelques répertoires et fichiers supplémentaires pour vérifier. J'étais aussi ennuyé, alors j'ai ajouté la dernière heure modifiée et MD5. Vous cherchez peut-être des doublons.

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
la source
-1

Cela nécessiterait un peu de logique.

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

Vous pouvez également l'adapter pour qu'il tienne dans un seul revêtement en utilisant des blocs de code.

EDIT: Bash est dur. basedirn'est pas une commande, dirnamene fait pas ce que je pensais, alors allons-y avec l'expansion des paramètres.

Zach Sanchez
la source
Ce serait parce que je ne me souviens apparemment pas des commandes bash ou de leur fonctionnement.
Zach Sanchez
dirnameest la commande que vous recherchez, et les affectations ne peuvent pas avoir d’espaces autour du =.
Kusalananda
J'ai découvert cela assez rapidement après avoir été signalé, et les espaces étaient une faute de frappe.
Zach Sanchez
Cela interrompt toutes sortes de noms de fichiers, en particulier les espaces. Ne pas analyser la sortie de ls ou trouver . Voir les autres réponses ici pour des approches sensées.
Gilles 'SO- arrête d'être méchant'
Ah, putain, vous avez raison, j'aurais pensé que la boucle for serait énumérée par une nouvelle ligne, pas par des espaces arbitraires. Je brise cette règle tout le temps car je rencontre rarement des fichiers ou des répertoires avec des espaces, mon mauvais.
Zach Sanchez