Найти только те папки, которые содержат файл с тем же именем, что и папка

8

Я хочу найти все подпапки, которые содержат файл уценки с тем же именем (и расширением .md).

Например: я хочу найти следующие подпапки:

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
  • Примечание. В каталоге могут быть другие файлы или подкаталоги.

Какие-либо предложения?


Решения проблемы могут быть проверены с использованием следующего кода:

#!/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
Относительно ваших заключительных замечаний. Обратите внимание, что некоторые ответы отличаются от других. Шахтное и Stéphane для примера, интерпретированы свой первый «Note» , как «если есть и другие уценки файлы в каталоге бы то ни было , не вернуть этот каталог» в то время как другие этого не делают (насколько я могу судить). Кроме того, только вы можете выбрать наиболее полезный для вас ответ . Ответы здесь будут продолжать получать положительные и отрицательные оценки после того, как вы примете ответ, в зависимости от того, что другие читатели считают наиболее полезным.
Кусалананда
Когда вы говорите: «Папки, содержащие файл разметки, имена которых не должны быть найдены», подразумеваете ли вы исключение каталогов с обоими? Например, если у вас есть foo/foo.mdи foo/bar.mdдолжны fooбыть включены или исключены?
Кевин
@Kevin В приведенном вами примере я хотел включить foo. Но, к сожалению, многие люди трактуют по-другому, и они это оправдывают. Итак, я подумал, что мне непонятно в общении. Итак, я принял ответ, который не включал foo.
Нихилу
Если вы используете -printfкоманду find, вы можете получить любую часть совпадения, какую захотите, см. Мое редактирование
Thor

Ответы:

13

Предполагая, что ваши файлы имеют разумное имя, то есть не нужно -print0и т. Д. Вы можете сделать это с помощью GNU:

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

Вывод:

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

Если вам нужно только имя каталога, добавьте -printfаргумент:

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

Вывод при запуске на ваших обновленных тестовых данных:

GeneratedTest/A/AA
GeneratedTest/A
GeneratedTest/C/CC2
GeneratedTest/C/CC1
GeneratedTest/B
Тор
источник
Даже без GNU найти:find . -type f | egrep '.*/([^/]+)/\1\.md$'
Джим Л.
3
@JimL. За исключением того, что передача его в инструмент, ориентированный на строки, будет ломать некоторые символы в именах файлов, например, перевод строки.
Кусалананда
1
@Kusalananda Согласен, однако, этот конкретный ответ основан на «разумно названных» файлах, которые не требуются print0.
Джим Л.
@Thor %hв printf используется для типа данных int, которые должны быть отформатированы. Ссылка: строка формата printf - Википедия . Не могли бы вы объяснить эту часть? Как %hздесь используется?
Нихилу
@Nikhil: Нет find, см. Раздел 3.2.2.1 в руководстве для более подробной информации.
Тор
6

В системе GNU вы можете сделать что-то вроде:

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
Вы не возражаете против включения предложенного решения Zsh в качестве альтернативы? было бы полезно для тех из нас, кто пытается узнать больше о zsh
steeldriver
Учитывая, что этот ответ получил больше голосов: тем, кто голосует против, не могли бы вы указать, почему это лучше, чем остальные? Это помогло бы мне выбрать наиболее подходящий ответ.
Нихилу
Стефан, я согласен с Steeldriver. zshУпомяните предыдущее решение (оно получило, я полагаю, два из голосов), и не стесняйтесь указывать на любые недостатки в нем, которые могли бы побудить вас удалить его.
Кусалананда
1
@steeldriver, в этом подходе zsh я (как и вы) пропустил ту часть требования, что dirs, содержащие другие md-файлы, следует исключить.
Стефан
@ StéphaneChazelas OP только что пояснил в комментариях, которые он на самом деле имел в виду, чтобы те были включены, это было просто плохо сформулировано, и люди восприняли это слишком буквально.
Кевин
6
find . -type d -exec sh -c '
    dirpath=$1
    set -- "$dirpath"/*.md
    [ -f "$dirpath/${dirpath##*/}.md" ] && [ "$#" -eq 1 ]' sh {} \; -print

Вышеприведенные команды найдут все каталоги ниже текущего каталога (включая текущий каталог) и выполнят короткий сценарий оболочки для каждого из них.

Код оболочки будет проверять, существует ли файл уценки с тем же именем, что и каталог внутри каталога, и является ли это единственным *.mdименем в этом каталоге. Если такой файл существует и если это единственное *.mdимя, встроенный сценарий оболочки завершается с нулевым статусом выхода. В противном случае он выходит с ненулевым состоянием выхода (ошибка сигнализации).

set -- "$dirpath"/*.mdБит будет устанавливать позиционные параметры списка путевых имен , соответствующих шаблону (соответствует любому имени с суффиксом .mdв каталоге). Затем мы можем использовать $#позже, чтобы увидеть, сколько совпадений мы получили из этого.

Если скрипт оболочки -printзавершится успешно, будет напечатан путь к найденному каталогу.

Немного более быстрая версия, которая использует меньше вызовов встроенного скрипта, но не позволяет вам делать больше с найденными путями внутри findсебя (хотя встроенный скрипт может быть дополнительно расширен):

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

Те же команды, но не обращая внимания на наличие других .mdфайлов в каталогах:

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

Смотрите также:

Кусалананда
источник
4

Или

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

или

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

Чтобы избежать запуска по одному shна файл.

Это find-shпроизвольная строка, которая становится нулевым позиционным параметром оболочки $0- создание чего-то запоминающегося может помочь с отладкой в ​​случае, если оболочка обнаружит ошибки (другие могут предложить использовать обычный shили даже _в качестве параметра «пропустить» по умолчанию).

steeldriver
источник
0

Вот мой. Я добавил еще несколько каталогов и файлов для проверки. Мне тоже было скучно, поэтому я добавил время последнего изменения и MD5. Может быть, вы ищете дубликаты.

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
источник
-1

Это потребует немного логики.

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

Вы также можете адаптировать это, чтобы вписаться в один вкладыш, используя блоки кода.

РЕДАКТИРОВАТЬ: Баш трудно. basedirне команда, dirnameне делает то, что я думал, так что давайте перейдем к расширению параметров.

Зак Санчес
источник
Это потому, что я, очевидно, не могу вспомнить команды bash или как они работают.
Зак Санчес
dirnameэто команда, которую вы ищете, и назначения не могут иметь пробелы вокруг =.
Кусалананда
Выяснили это довольно быстро после того, как на это указали, и места были опечаткой.
Зак Санчес
Это нарушает все виды имен файлов, особенно с пробелами. Не анализируйте вывод команды ls или find . Смотрите другие ответы здесь для разумных подходов.
Жиль "ТАК - перестань быть злым"
Ах, черт, вы правы, я бы подумал, что цикл for будет перечислять по новой строке, а не по произвольному пробелу. Я постоянно нарушаю это правило, потому что редко сталкиваюсь с файлами или каталогами с пробелами, мой плохой.
Зак Санчес,