我想递归地查找*.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
使用 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
答案2
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
.pdf
包含从find
命令返回的文件的完整路径"${file##*/}"
仅包含最后一个之后的部分/
,即仅包含文件的基本名称"${file%/*}"
包含到最终/
ie 的路径(结果的基本名称部分除外)"${path##*/}"
/
包含变量最后一个之后的部分path
,即文件基本名称上方的直接文件夹路径"${base%.*}"
.pdf
包含删除了扩展名的基本名称部分
因此,如果不带扩展名的基本名称与上面的直接文件夹的名称匹配,我们将打印路径。
答案3
的相反伊尼安的回答,即查找目录,然后查看它们是否包含具有特定名称的文件。
以下打印找到的文件相对于目录的路径名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 文件,则只会尝试检测其中一个文件,而不是根据该目录的名称测试所有 100 个文件的名称。
答案4
没有指定,但如果有人感兴趣的话,这里有一个没有正则表达式的解决方案。
我们可以使用find . -type f
来获取文件,然后利用dirname
和basename
来编写条件。这些实用程序具有以下行为:
$ 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
解决方案
将以上内容组合起来形成条件,然后仅在该条件返回 true 时"$(basename $file)" = "$(basename $(dirname $file))".pdf
打印每个结果。find
$ 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)