Suchen Sie nur die Ordner, die eine Datei mit demselben Namen wie der Ordner enthalten

8

Ich möchte alle Unterordner finden, die eine Markdown-Datei mit demselben Namen (und derselben Erweiterung .md) enthalten.

Zum Beispiel: Ich möchte folgende Unterordner suchen:

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
  • Hinweis: Das Verzeichnis kann andere Dateien oder Unterverzeichnisse enthalten.

Irgendwelche Vorschläge?


Die Lösungen für das Problem können mit dem folgenden Code getestet werden:

#!/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
quelle
1
In Bezug auf Ihre letzten Bemerkungen. Beachten Sie, dass einige Antworten andere Funktionen haben als andere. Mine und Stéphane zum Beispiel interpretierte die ersten „Hinweis“ , wie „wenn es andere Abschläge Dateien in dem Verzeichnis zu löschen , nicht zurück das Verzeichnis“ , während die andere nicht (soweit ich sehen kann). Abgesehen davon können nur Sie die Antwort auswählen, die für Sie am hilfreichsten ist . Die Antworten hier erhalten weiterhin Stimmen nach oben und unten, nachdem Sie eine Antwort akzeptiert haben, je nachdem, was andere Leser am nützlichsten finden.
Kusalananda
Wenn Sie sagen "Ordner mit Markdown-Dateien, deren Namen unterschiedlich sind, sollten nicht gefunden werden", möchten Sie Verzeichnisse mit beiden ausschließen? ZB wenn Sie haben foo/foo.mdoder foo/bar.mdsollten fooeingeschlossen oder ausgeschlossen werden?
Kevin
@ Kevin In dem Beispiel, das du gegeben hast, hatte ich vorgehabt, foo aufzunehmen. Aber leider haben viele Menschen anders interpretiert und das gerechtfertigt. Also dachte ich, dass ich in der Kommunikation nicht klar war. Also akzeptierte ich eine Antwort, die foo nicht enthielt.
Nikhil
Wenn Sie -printfmit find verwenden, können Sie jeden Teil des Spiels erhalten, den Sie wollen, siehe meine Bearbeitung
Thor

Antworten:

13

Vorausgesetzt, Ihre Dateien haben einen vernünftigen Namen, dh keine Notwendigkeit -print0usw. Sie können dies mit GNU find folgendermaßen tun:

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

Ausgabe:

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

Wenn Sie nur den Verzeichnisnamen möchten, fügen Sie ein -printfArgument hinzu:

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

Ausgabe bei Ausführung mit Ihren aktualisierten Testdaten:

GeneratedTest/A/AA
GeneratedTest/A
GeneratedTest/C/CC2
GeneratedTest/C/CC1
GeneratedTest/B
Thor
quelle
Auch ohne GNU finden:find . -type f | egrep '.*/([^/]+)/\1\.md$'
Jim L.
3
@ JimL. Abgesehen davon, dass das Weiterleiten an ein zeilenorientiertes Werkzeug bei einigen Zeichen in Dateinamen wie Zeilenumbruch unterbrochen wird.
Kusalananda
1
@Kusalananda Einverstanden ist jedoch, dass diese spezielle Antwort auf "vernünftig benannten" Dateien basiert, die nicht benötigt werden print0.
Jim L.
@Thor %hin printf wird für den zu formatierenden Datentyp int verwendet. Referenz: printf Format String - Wikipedia . Könnten Sie bitte diesen Teil erklären? Wie wird %hhier verwendet?
Nikhil
@Nikhil: Nicht mit find, siehe Abschnitt 3.2.2.1 im Handbuch für weitere Details.
Thor
6

Auf einem GNU-System können Sie Folgendes tun:

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
quelle
3
Würde es Ihnen etwas ausmachen, Ihre vorgeschlagene zsh-Lösung als Alternative wieder aufzunehmen? Es wäre hilfreich für diejenigen von uns, die versuchen, mehr über zsh
steeldriver
Angesichts der Tatsache, dass diese Antwort mehr Stimmen erhalten hat: Könnten Sie bitte denjenigen, die diese Antwort befürworten, angeben, warum dies besser ist als die anderen? Es würde mir helfen, die am besten geeignete Antwort zu wählen.
Nikhil
Stéphane, ich stimme dem Stahlfahrer zu. Erwähnen Sie die vorherige zshLösung (ich glaube, sie hat zwei der positiven Stimmen erhalten) und weisen Sie auf Mängel hin, die Sie möglicherweise dazu veranlasst haben, sie zu entfernen.
Kusalananda
1
@steeldriver, in diesem zsh-Ansatz hatte ich (wie Sie) den Teil der Anforderung übersehen, dass Verzeichnisse, die andere md-Dateien enthalten, weggelassen werden sollten.
Stéphane Chazelas
@ StéphaneChazelas OP hat gerade in den Kommentaren klargestellt, dass er eigentlich für diejenigen gedacht ist, die aufgenommen werden sollen. Es war nur schlecht formuliert und die Leute haben es zu wörtlich genommen.
Kevin
6
find . -type d -exec sh -c '
    dirpath=$1
    set -- "$dirpath"/*.md
    [ -f "$dirpath/${dirpath##*/}.md" ] && [ "$#" -eq 1 ]' sh {} \; -print

Das Obige würde alle Verzeichnisse unterhalb des aktuellen Verzeichnisses (einschließlich des aktuellen Verzeichnisses) finden und für jedes ein kurzes Shell-Skript ausführen.

Der Shell-Code würde testen, ob es eine Markdown-Datei mit demselben Namen wie das Verzeichnis im Verzeichnis gibt und ob dies der einzige *.mdName in diesem Verzeichnis ist. Wenn eine solche Datei vorhanden ist und dies der einzige *.mdName ist, wird das Inline-Shell-Skript mit einem Exit-Status von Null beendet. Andernfalls wird es mit einem Exit-Status ungleich Null beendet (Signalisierungsfehler).

Das set -- "$dirpath"/*.mdBit setzt die Positionsparameter auf die Liste der Pfadnamen, die dem Muster entsprechen (entspricht jedem Namen mit einem Suffix .mdim Verzeichnis). Wir können dann $#später verwenden, um zu sehen, wie viele Übereinstimmungen wir daraus erhalten haben.

Wenn das Shell-Skript erfolgreich beendet wird, -printwird der Pfad zum gefundenen Verzeichnis gedruckt.

Etwas schnellere Version, die weniger Aufrufe des Inline-Skripts verwendet, aber nicht mehr mit den gefundenen Pfadnamen findselbst tun kann (das Inline-Skript kann jedoch weiter erweitert werden):

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

Dieselben Befehle, ohne sich darum zu kümmern, ob sich andere .mdDateien in den Verzeichnissen befinden:

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

Siehe auch:

Kusalananda
quelle
4

Entweder

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

oder

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

Um zu vermeiden, dass eine shpro Datei ausgeführt wird.

Dies find-shist eine willkürliche Zeichenfolge, die zum nullten Positionsparameter der Shell wird. Wenn $0Sie etwas Fehlerhaftes feststellen, kann dies beim Debuggen hilfreich sein, falls die Shell auf Fehler stößt (andere empfehlen möglicherweise die Verwendung von Plain shoder sogar _als Standardparameter "Überspringen").

Steeldriver
quelle
0

Hier ist meins. Ich habe weitere Verzeichnisse und Dateien hinzugefügt, um dies zu überprüfen. Ich war auch gelangweilt, also habe ich die letzte geänderte Zeit und MD5 hinzugefügt. Vielleicht suchen Sie nach Duplikaten.

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
quelle
-1

Dies würde ein wenig Logik erfordern.

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

Sie können dies auch mithilfe von Codeblöcken so anpassen, dass es in einen Einzeiler passt.

EDIT: Bash ist schwer. basedirist kein Befehl, dirnamemacht nicht das, was ich dachte, also lass uns mit der Parametererweiterung fortfahren.

Zach Sanchez
quelle
Das liegt daran, dass ich mich anscheinend nicht an Bash-Befehle erinnern kann oder wie sie funktionieren.
Zach Sanchez
dirnameist der Befehl, nach dem Sie suchen, und Zuweisungen dürfen keine Leerzeichen um das enthalten =.
Kusalananda
Fand das ziemlich schnell heraus, nachdem darauf hingewiesen wurde, und die Leerzeichen waren ein Tippfehler.
Zach Sanchez
Dies funktioniert bei allen Arten von Dateinamen, insbesondere bei Leerzeichen. Analysieren Sie nicht die Ausgabe von ls oder find . In den anderen Antworten finden Sie sinnvolle Ansätze.
Gilles 'SO - hör auf böse zu sein'
Ah, verdammt, Sie haben Recht, ich hätte gedacht, die for-Schleife würde durch Zeilenumbruch und nicht durch willkürliches Leerzeichen aufgezählt. Ich verstoße ständig gegen diese Regel, weil ich selten auf Dateien oder Verzeichnisse mit Leerzeichen stoße, mein schlechtes.
Zach Sanchez