仅查找那些包含与该文件夹同名的文件的文件夹


8

我想找到所有包含具有相同名称(和扩展名.md)的markdown文件的子文件夹。

例如:我要查找以下子文件夹:

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
关于你的最后发言。请注意,某些答案的作用与其他答案不同。矿山和斯特凡的,例如,您的解释第一个“注”为“是否有其他的降价目录中的文件无论如何,不要返回目录”,而有的则没有(据我可以看到)。除此之外,只有您可以选择对您最有帮助的答案。您接受答案后,此处的答案将继续获得上下投票,具体取决于其他读者认为最有用的内容。
库萨兰达

当您说“找不到包含名称不同的markdown文件的文件夹”时,您是要排除两个目录吗?例如,如果您有foo/foo.md并且foo/bar.md应该foo被包括或排除在外?
凯文

@Kevin在您提供的示例中,我打算包含foo。但是不幸的是,许多人用另一种方式解释了,并证明了这一点。所以,我以为我不清楚沟通。因此,我接受了不包含foo的答案。
Nikhil

如果-printf与find配合使用,则可以获取匹配项的任意部分,请参阅我的编辑
托尔(Thor)

Answers:


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$'
Jim L.

3
@JimL。除了将其管道传输到面向行的工具外,还会破坏文件名中的某些字符,例如换行符。
库萨兰达

1
@Kusalananda同意,但是,此特定答案是基于不需要的“明智命名”文件的print0
Jim L.

%hprintf中的@Thor 用于要格式化的int类型数据。参考:printf格式字符串-Wikipedia。你能解释一下那部分吗?%h在这里如何使用?
Nikhil

@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

鉴于此答案获得了更多选票:对于赞成该答案的人,您能否说明为什么它比其他答案更好?这将帮助我选择最合适的答案。
Nikhil

斯特凡(Stéphane),我同意钢铁司机。请提及先前的zsh解决方案(我相信它有两个建议),请随时指出其中可能提示您删除它的任何缺陷。
库萨兰达

1
@steeldriver,在我(与您一样)的zsh方法中,我错过了部分要求删除包含其他md文件的目录的要求。
斯特凡·查泽拉斯

@StéphaneChazelasOP只是他实际上要包含在其中的注释中澄清了这一点,它的措辞很差,人们从字面上看也是如此。
凯文

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

上面的代码将找到当前目录下的所有目录(包括当前目录),并对每个目录执行一个简短的shell脚本。

shell代码将测试是否有一个markdown文件与该目录中的目录同名,以及这是否是该*.md目录中的唯一名称。如果存在这样的文件,并且是唯一的*.md名,则内联Shell脚本以零退出状态退出。否则,它将以非零退出状态退出(信号发送失败)。

set -- "$dirpath"/*.md位会将位置参数设置为与模式匹配的路径名列表(与.md目录中带后缀的任何名称匹配)。然后我们可以使用$#来查看从中获得了多少匹配项。

如果shell脚本成功退出,-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,甚至_用作默认的“跳过”参数)。


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

-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的输出。请参阅此处的其他答案以获取明智的方法。
吉尔(Gilles)'所以

啊,该死,你是对的,我本以为for循环将由换行符枚举,而不是由任意空格枚举。我一直违反该规则,因为我很少遇到带有空格的文件或目录,这很糟糕。
扎克·桑切斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.