Trova solo le cartelle che contengono un file con lo stesso nome della cartella


8

Voglio trovare tutte le sottocartelle, che contengono un file markdown con lo stesso nome (ed estensione .md).

Ad esempio: voglio trovare le seguenti sottocartelle:

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: nella directory possono essere presenti altri file o sottodirectory.

Eventuali suggerimenti?


Le soluzioni al problema possono essere testate utilizzando il seguente codice:

#!/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 "$@"

1
Per quanto riguarda le tue osservazioni finali. Nota che alcune risposte fanno cose diverse da altre. Mine and Stéphane di, ad esempio, interpretato la tua prima "Nota" come "se ci sono altri file nella directory di riduzione dei prezzi di sorta , non voler tornare quella directory", mentre gli altri non (per quanto posso vedere). A parte questo, solo tu puoi scegliere la risposta che ti è più utile . Le risposte qui continueranno a ricevere voti su e giù dopo aver accettato una risposta, a seconda di ciò che gli altri lettori trovano più utili.
Kusalananda

Quando dici "Le cartelle che contengono un file di markdown i cui nomi sono diversi non devono essere trovate", intendi escludere le directory con entrambi? Ad esempio se hai foo/foo.mde foo/bar.mddovresti fooessere incluso o escluso?
Kevin,

@Kevin Nell'esempio che hai dato, avevo intenzione di includere foo. Ma sfortunatamente molte persone hanno interpretato diversamente e l'hanno giustificato. Quindi, ho pensato che non ero chiaro nella comunicazione. Quindi, ho accettato una risposta che non includeva foo.
Nikhil,

Se lo usi -printfcon find, puoi ottenere qualsiasi parte della partita che desideri, vedi la mia modifica
Thor

Risposte:


13

Supponendo che i tuoi file siano ragionevolmente nominati, cioè non sia necessario -print0ecc. Puoi farlo con GNU trovare in questo modo:

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

Produzione:

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

Se si desidera solo il nome della directory, aggiungere un -printfargomento:

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

Output quando eseguito sui dati di test aggiornati:

GeneratedTest/A/AA
GeneratedTest/A
GeneratedTest/C/CC2
GeneratedTest/C/CC1
GeneratedTest/B

Anche senza GNU trovare:find . -type f | egrep '.*/([^/]+)/\1\.md$'
Jim L.

3
@JimL. Solo che il piping su uno strumento orientato alla linea si spezzerebbe su alcuni personaggi nei nomi dei file, come newline.
Kusalananda

1
@Kusalananda D'accordo, tuttavia, questa risposta particolare si basa su file "con un nome ragionevole" che non richiedono print0.
Jim L.

@Thor %hin printf viene utilizzato per il tipo di dati int da formattare. Riferimento: stringa di formato printf - Wikipedia . Potresti spiegare per favore quella parte? Come viene %hutilizzato qui?
Nikhil

@Nikhil: Non con find, vedere la sezione 3.2.2.1 nel manuale per maggiori dettagli.
Thor

6

Su un sistema GNU, potresti fare qualcosa del tipo:

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}'

3
ti dispiacerebbe includere nuovamente la soluzione zsh proposta come alternativa? sarebbe utile per quelli di noi che cercano di saperne di più su zsh
steeldriver il

Dato che questa risposta ha ricevuto più voti: A coloro che stanno votando questa risposta, potresti specificare perché questo è migliore del resto? Mi aiuterebbe a scegliere la risposta più adatta.
Nikhil,

Stéphane, sono d'accordo con Steeldriver. zshIndica la soluzione precedente (ho ottenuto, credo, due dei voti positivi) e sentiti libero di evidenziare eventuali difetti che potrebbero averti spinto a rimuoverlo.
Kusalananda

1
@steeldriver, in quell'approccio zsh io (come te) avevo perso la parte del requisito secondo cui le directory che contengono altri file md dovrebbero essere omesse.
Stéphane Chazelas,

@ StéphaneChazelas OP ha appena chiarito nei commenti che in realtà intendeva includere quelli, era semplicemente mal formulato e la gente lo prendeva troppo alla lettera.
Kevin,

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

Quanto sopra troverà tutte le directory sotto la directory corrente (inclusa la directory corrente) ed eseguirà uno script di shell corta per ognuna.

Il codice shell verificherebbe se esiste un file markdown con lo stesso nome della directory all'interno della directory e se questo è l'unico *.mdnome in quella directory. Se esiste un file di questo tipo e se è l'unico *.mdnome, lo script di shell inline esce con uno stato di uscita pari a zero. Altrimenti esce con uno stato di uscita diverso da zero (segnalazione non riuscita).

Il set -- "$dirpath"/*.mdbit imposterà i parametri posizionali sull'elenco dei nomi di percorso corrispondenti al modello (corrisponde a qualsiasi nome con un suffisso .mdnella directory). Possiamo quindi utilizzare in $#seguito per vedere quante partite abbiamo ottenuto da questo.

Se lo script della shell viene chiuso correttamente, -printstamperà il percorso della directory trovata.

Versione leggermente più veloce che utilizza un minor numero di invocazioni dello script inline, ma ciò non consente di fare di più con i percorsi trovati in findsé (lo script inline può essere ulteriormente espanso):

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

Gli stessi comandi, ma senza preoccuparsi se ci sono altri .mdfile nelle directory:

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

Guarda anche:


4

O

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

o

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

Per evitare di eseguirne uno shper file.

Si find-shtratta di una stringa arbitraria che diventa il parametro posizionale zeroth della shell $0- renderla qualcosa di memorabile può aiutare con il debug nel caso in cui la shell incontri errori (altri potrebbero suggerire di usare il parametro "skip" predefinito sho addirittura _).


0

Ecco il mio. Ho aggiunto altre directory e file da verificare. Ero anche annoiato, quindi ho aggiunto l'ultima volta modificata e MD5. Forse stai cercando duplicati.

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

-1

Ciò richiederebbe un po 'di logica.

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

Puoi anche adattarlo per adattarlo a un solo liner usando i blocchi di codice.

EDIT: Bash è difficile. basedirnon è un comando, dirnamenon fa quello che pensavo avesse fatto, quindi andiamo con l'espansione dei parametri.


Questo perché a quanto pare non ricordo i comandi di bash o come funzionano.
Zach Sanchez,

dirnameè il comando che stai cercando e le assegnazioni non possono avere spazi attorno a =.
Kusalananda

L'ho scoperto abbastanza rapidamente dopo che è stato sottolineato, e gli spazi erano un errore di battitura.
Zach Sanchez,

Ciò si interrompe su tutti i tipi di nomi di file, in particolare con spazi. Non analizzare l'output di ls o find . Vedi le altre risposte qui per approcci sensibili.
Gilles 'SO- smetti di essere malvagio' il

Ah, dannazione, hai ragione, avrei pensato che il ciclo for sarebbe enumerato da newline, non da spazi bianchi arbitrari. Ho infranto questa regola continuamente perché raramente incontro file o directory con spazi, mio ​​male.
Zach Sanchez,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.