Busque solo aquellas carpetas que contienen un archivo con el mismo nombre que la carpeta

8

Quiero encontrar todas las subcarpetas, que contienen un archivo de descuento con el mismo nombre (y extensión .md).

Por ejemplo: Quiero buscar las siguientes subcarpetas:

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: Puede haber otros archivos o subdirectorios en el directorio.

¿Alguna sugerencia?


Las soluciones al problema se pueden probar con el siguiente 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
fuente
1
En cuanto a sus comentarios finales. Tenga en cuenta que algunas respuestas hacen cosas diferentes de otras. Mina y Stéphane de, por ejemplo, interpreta su primera "nota" como "si hay otros archivos en el directorio de rebajas en absoluto , no regrese ese directorio", mientras que los otros no lo hacen (por lo que puedo ver). Aparte de eso, solo usted puede elegir la respuesta que sea más útil para usted . Las respuestas aquí continuarán recibiendo votos positivos y negativos después de que haya aceptado una respuesta, dependiendo de lo que otros lectores encuentren más útil.
Kusalananda
Cuando dice "No se deben encontrar las carpetas que contienen archivos de descuento cuyos nombres son diferentes", ¿quiere decir que excluye directorios con ambos? Por ejemplo, si tiene foo/foo.mdy foo/bar.mddebe fooser incluido o excluido?
Kevin
@Kevin En el ejemplo que diste, tenía la intención de incluir foo. Pero desafortunadamente muchas personas interpretaron de otra manera y lo justificaron. Entonces, pensé que no estaba claro en la comunicación. Entonces, acepté la respuesta que no incluía foo.
Nikhil
Si lo usas -printfcon find, puedes obtener cualquier parte de la partida que quieras, mira mi edición
Thor

Respuestas:

13

Asumiendo que sus archivos tienen un nombre sensato, es decir, no es necesario, -print0etc. Puede hacer esto con GNU find así:

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

Salida:

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

Si solo desea el nombre del directorio, agregue un -printfargumento:

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

Salida cuando se ejecuta en sus datos de prueba actualizados:

GeneratedTest/A/AA
GeneratedTest/A
GeneratedTest/C/CC2
GeneratedTest/C/CC1
GeneratedTest/B
Thor
fuente
Incluso sin GNU encontrar:find . -type f | egrep '.*/([^/]+)/\1\.md$'
Jim L.
3
@JimL. Excepto que canalizarlo a una herramienta orientada a líneas se rompería en algunos caracteres en los nombres de archivo, como newline.
Kusalananda
1
@Kusalananda De acuerdo, sin embargo, esta respuesta particular se basa en archivos "con un nombre sensato" que no requieren print0.
Jim L.
@Thor %hen printf se usa para el tipo de datos int a formatear. Referencia: cadena de formato printf - Wikipedia . ¿Podría explicar esa parte? ¿Cómo se %husa aquí?
Nikhil
@Nikhil: No con find, consulte la sección 3.2.2.1 en el manual para obtener más detalles.
Thor
6

En un sistema GNU, podría hacer 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
fuente
3
¿le importaría volver a incluir su solución zsh propuesta como alternativa? sería de gran ayuda para aquellos de nosotros tratando de aprender más sobre zsh
steeldriver
Dado que esta respuesta ha recibido más votos: Para aquellos que están votando esta respuesta, ¿podrían especificar por qué esto es mejor que el resto? Me ayudaría a elegir la respuesta más adecuada.
Nikhil
Stéphane, estoy de acuerdo con Steeldriver. Mencione la zshsolución anterior (creo que recibió dos de los votos a favor), y siéntase libre de señalar cualquier falla que pueda haberlo llevado a eliminarla.
Kusalananda
1
@steeldriver, en ese enfoque zsh, yo (como usted) había omitido la parte del requisito de que los directorios que contienen otros archivos md deberían omitirse.
Stéphane Chazelas
@ StéphaneChazelas OP acaba de aclarar en los comentarios que realmente quería que se incluyeran, solo estaba mal redactado y la gente lo tomó demasiado literalmente.
Kevin
6
find . -type d -exec sh -c '
    dirpath=$1
    set -- "$dirpath"/*.md
    [ -f "$dirpath/${dirpath##*/}.md" ] && [ "$#" -eq 1 ]' sh {} \; -print

Lo anterior encontraría todos los directorios debajo del directorio actual (incluido el directorio actual) y ejecutaría un script de shell corto para cada uno.

El código de shell probaría si hay un archivo de descuento con el mismo nombre que el directorio dentro del directorio, y si este es el único *.mdnombre en ese directorio. Si existe dicho archivo y es el único *.mdnombre, el script de shell en línea sale con un estado de salida cero. De lo contrario, sale con un estado de salida distinto de cero (falla de señalización).

El set -- "$dirpath"/*.mdbit establecerá los parámetros posicionales en la lista de nombres de ruta que coinciden con el patrón (coincide con cualquier nombre con un sufijo .mden el directorio). Luego podemos usar $#más tarde para ver cuántas coincidencias obtuvimos de esto.

Si el script de shell se cierra correctamente, -printimprimirá la ruta al directorio encontrado.

Versión un poco más rápida que utiliza menos invocaciones del script en línea, pero eso no le permite hacer más con los nombres de ruta encontrados en findsí mismos (aunque el script en línea puede expandirse aún más):

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

Los mismos comandos pero sin importar si hay otros .mdarchivos en los directorios:

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

Ver también:

Kusalananda
fuente
4

Ya sea

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

Para evitar ejecutar uno shpor archivo.

El find-shes una cadena arbitraria que se convierte en el parámetro posicional cero de la shell, $0por lo que es algo memorable puede ayudar con la depuración en caso de que la shell encuentre errores (otros pueden sugerir el uso de un parámetro de "omisión" simple sho incluso _predeterminado).

conductor de acero
fuente
0

Aquí está el mío. Agregué algunos directorios y archivos más para verificar. También estaba aburrido, así que agregué la última hora modificada y MD5. Tal vez estás buscando duplicados.

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
usuario208145
fuente
-1

Esto requeriría un poco de lógica.

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

También puede adaptarlo para que quepa en una línea utilizando bloques de código.

EDITAR: Bash es difícil. basedirno es un comando, dirnameno hace lo que pensé que hizo, así que vamos con la expansión de parámetros.

Zach Sanchez
fuente
Eso sería porque aparentemente no puedo recordar los comandos bash o cómo funcionan.
Zach Sanchez
dirnamees el comando que está buscando y las asignaciones no pueden tener espacios alrededor del =.
Kusalananda
Descubrí eso bastante rápido después de señalarlo, y los espacios eran un error tipográfico.
Zach Sanchez
Esto se rompe en todo tipo de nombres de archivos, especialmente con espacios. No analice la salida de ls o find . Vea las otras respuestas aquí para obtener enfoques razonables.
Gilles 'SO- deja de ser malvado'
Ah, maldición, tienes razón, habría pensado que el bucle for se enumeraría por nueva línea, no por espacios en blanco arbitrarios. Rompo esa regla todo el tiempo porque rara vez encuentro archivos o directorios con espacios, mi mal.
Zach Sanchez el