查找具有给定扩展名的所有文件,这些扩展名的基本名称是父目录的名称


9

我想递归查找*.pdf目录中~/foo的每个文件,其基本名称与文件的父目录的名称匹配。

例如,假设目录结构~/foo如下所示

foo
├── dir1
│   ├── dir1.pdf
│   └── dir1.txt
├── dir2
│   ├── dir2.tex
│   └── spam
│       └── spam.pdf
└── dir3
    ├── dir3.pdf
    └── eggs
        └── eggs.pdf

运行我想要的命令将返回

~/foo/dir1/dir1.pdf
~/foo/dir2/spam/spam.pdf
~/foo/dir3/dir3.pdf
~/foo/dir3/eggs/eggs.pdf

是否可以使用find或其他一些核心实用程序?我假设使用-regex选项可以做到这一点,find但是我不确定如何编写正确的模式。


是的,我现在将模拟一个例子。
布赖恩·菲茨帕特里克

1
@Inian添加了一个示例。这有帮助吗?
布赖恩·菲茨帕特里克

Answers:


16

使用GNU find

find . -regextype egrep -regex '.*/([^/]+)/\1\.pdf'
  • -regextype egrep 使用egrep样式正则表达式。
  • .*/ 匹配祖父母的导演。
  • ([^/]+)/ 匹配组中的父目录。
  • \1\.pdf用于backreference匹配文件名作为父目录。

更新

一个(我自己就是一个)可能会觉得.*足够贪婪,没有必要/从父匹配中排除:

find . -regextype egrep -regex '.*/(.+)/\1\.pdf'

上面的命令不能很好地工作,因为它会发生数学运算./a/b/a/b.pdf

  • .*/ 火柴 ./
  • (.+)/ 火柴 a/b/
  • \1.pdf 火柴 a/b.pdf

很酷。希望我能很好地进行正则表达式。
布赖恩·菲茨帕特里克

或者find . -regex '.*/\([^/]*\)/\1\.pdf'然后它甚至可以与BSD一起使用find
斯特凡Chazelas

7

find .. -exec sh -c ''使用shell构造来匹配基本名称的传统循环变体和上面的直接路径将在下面执行。

find foo/ -name '*.pdf' -exec sh -c '
    for file; do 
        base="${file##*/}"
        path="${file%/*}"
        if [ "${path##*/}" =  "${base%.*}" ]; then
            printf "%s\n" "$file" 
        fi
    done' sh {} +

分解单个参数扩展

  • file包含.pdffind命令返回的文件的完整路径
  • "${file##*/}"仅包含最后一个之后的部分,/即仅文件的基名
  • "${file%/*}"包含到达最终路径的路径,/即结果的basename部分除外
  • "${path##*/}"包含在最后部分/path变量,即:该文件的基本名称上面立即文件夹路径
  • "${base%.*}"包含基本名称的一部分,.pdf扩展名已删除

因此,如果不带扩展名的基本名称与上面的直接文件夹的名称匹配,我们将打印路径。


7

Inian的回答相反,即查找目录,然后查看它们是否包含具有特定名称的文件。

以下显示相对于目录的找到文件的路径名foo

find foo -type d -exec sh -c '
    for dirpath do
        pathname="$dirpath/${dirpath##*/}.pdf"
        if [ -f "$pathname" ]; then
            printf "%s\n" "$pathname"
        fi
    done' sh {} +

${dirpath##*/}将被目录路径的文件名部分替换,并可能被替换$(basename "$dirpath")

对于喜欢短路语法的人:

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

这样做的好处是您的PDF文件可能比目录更多。如果将一个查询限制为较小的数量(目录数量),则所涉及的测试数量将减少。

例如,如果单个目录包含100个PDF文件,则这只会尝试检测其中一个PDF文件,而不是对照目录中的所有100个文件名进行测试。


3

zsh

printf '%s\n' **/*/*.pdf(e@'[[ $REPLY:t = $REPLY:h:t.pdf ]]'@)

请注意,虽然**/不会遵循符号链接,但*/会。


2

它没有指定,但是如果有人感兴趣,这里是不带正则表达式的解决方案。

我们可以find . -type f用来获取文件,然后利用dirnamebasename编写条件文件。该实用程序具有以下行为:

$ find . -type f
./dir2/spam/spam.pdf
./dir2/dir2.tex
./dir3/dir3.pdf
./dir3/eggs/eggs.pdf
./dir1/dir1.pdf
./dir1/dir1.txt

basename仅返回最后一个文件名之后的文件名/

$ for file in $(find . -type f); do basename $file; done
spam.pdf
dir2.tex
dir3.pdf
eggs.pdf
dir1.pdf
dir1.txt

dirname给出了到达最终路径的完整路径/

$ for file in $(find . -type f); do dirname $file; done
./dir2/spam
./dir2
./dir3
./dir3/eggs
./dir1
./dir1

因此,basename $(dirname $file)给出文件的父目录。

$ for file in $(find . -type f); do basename $(dirname $file) ; done
spam
dir2
dir3
eggs
dir1
dir1

结合以上内容形成条件"$(basename $file)" = "$(basename $(dirname $file))".pdf,然后仅find在条件返回true 时才打印每个结果。

$ while read file; do if [ "$(basename "$file")" = "$(basename "$(dirname "$file")")".pdf ]; then echo $file; fi done < <(find . -type f)
./dir2/spam/spam.pdf
./dir3/dir3.pdf
./dir3/eggs/eggs.pdf
./dir1/dir1.pdf
./Final Thesis/grits/grits.pdf
./Final Thesis/Final Thesis.pdf

在上面的示例中,我们添加了名称中带有空格的目录/文件来处理这种情况(由于注释中的@Kusalananda)


不幸的是,这将破坏文件名,例如Final Thesis.pdf(带有空格)。
库萨兰达

@Kusalananda固定。
user1717828

0

我每天在Find程序中进行bash遍历,字符串测试的简单循环。称我为非理性人,虽然它可能不是最佳选择,但这种简单的代码却为我解决了问题:可读性和可重用性,甚至令人满意!因此,请允许我提出以下建议的组合:

•bash globstarfor f in ** ; do ... **循环遍历当前目录中的所有文件以及所有子文件夹..以检查当前会话中的globstar状态:shopt -p globstar。激活globstar :shopt -s globstar

•“文件”实用性if [[ $(file "$f") =~ pdf ]]; then ... 检查pdf的实际文件格式-比仅测试文件扩展名更强大

•basename,dirname:将文件名与紧接其上的目录名进行比较。basename返回文件名- dirname返回整个目录路径-结合这两个函数,仅返回包含匹配文件的一个目录。我将每个变量放在一个变量(_mydir_myf)中,然后使用=〜进行简单的测试以进行字符串匹配。

一种实用性:删除文件名中的任何“点”,以避免将文件名与快捷方式也是“”的当前目录匹配。-我在变量_myf上使用了直接字符串替换:${_myf//./}-不是很优雅,但可以。正匹配项将在输出之前加上:,以返回每个文件的路径-以及当前文件夹的完整路径$(pwd)/

for f in ** ; do
  if [[ $(file "$f") =~ PDF ]]; then
    _mydir="$(basename $(dirname $f))" ; 
    _myf="$(basename $f)" ; 
    [[ "${_myf//./}" =~ "$_mydir" ]] && echo -e "$(pwd)/$f" ; 
  fi ; 
done
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.