获取包含文件名包含字符串的子目录的列表


45

如何获得包含名称与特定模式匹配的文件的子目录列表?

更具体地说,我正在寻找包含文件名中带有字母“ f”的文件的目录。

理想情况下,列表将没有重复项,仅包含不带文件名的路径。

Answers:


43
find . -type f -name '*f*' | sed -r 's|/[^/]+$||' |sort |uniq

上面的代码查找当前目录(.)下的所有文件,这些文件是常规文件(-type f),并且文件名()中有f某处-name '*f*'。接下来,sed删除文件名,仅保留目录名。然后,对目录列表进行排序(sort),并删除重复项(uniq)。

sed命令由单个替代项组成。它查找与正则表达式/[^/]+$匹配的内容,并替换所有不匹配的内容。美元符号表示行尾。 [^/]+'表示一个或多个非斜杠字符。因此,/[^/]+$表示从最后一个斜杠到行尾的所有字符。换句话说,这与完整路径末尾的文件名匹配。因此,sed命令删除文件名,而文件所在目录的名称保持不变。

简化版

许多现代sort命令都支持-u标志,这使得uniq不必要。对于GNU sed:

find . -type f -name '*f*' | sed -r 's|/[^/]+$||' |sort -u

并且,对于MacOS sed:

find . -type f -name '*f*' | sed -E 's|/[^/]+$||' |sort -u

另外,如果您的find命令支持,则可以find直接打印目录名称。这避免了以下需求sed

find . -type f -name '*f*' -printf '%h\n' | sort -u

更强大的版本(需要GNU工具)

上面的版本会被包含换行符的文件名混淆。一个更强大的解决方案是对NUL终止的字符串进行排序:

find . -type f -name '*f*' -printf '%h\0' | sort -zu | sed -z 's/$/\n/'

我有很多文件,使它们的排序成本太高。投掷uniq到混合通过删除重复的线路是已经紧挨着彼此有很大帮助。 find . -type f -name '*f*' -printf '%h\0' | uniq -z | sort -zu | tr '\0' '\n'。或者,如果您的工具较旧,则uniq可能没有-z选项。find . -type f -name '*f*' -printf '%h\n' | uniq | sort -u
jbo5112

1
MacOS用户:sed标志不是-r。由于某种原因,它是-E
David

@David非常正确。答案已更新-E为MacOS 显示。
John1024 '18

22

为什么不试试这个:

find / -name '*f*' -printf "%h\n" | sort -u

最佳答案。完全兼容POSIX,与上面的某些回答不同,它还获得了特别的最短管道奖:)。
kkm

我很乐意看到某人展示了与其他人相比的时间安排,因为我觉得这是迄今为止最快的。
dlamblin

4
@kkm我同意这是最好的解决方案,但是POSIX规范find实际上很稀疏- 指定-printf运算符。这不适用于BSD 。因此,不是 “完全与POSIX兼容”。(尽管在POSIX中。)findsort -u
通配符

8

基本上可以使用两种方法来执行此操作。一个将解析字符串,而另一个将对每个文件进行操作。解析字符串中使用的工具,例如grepsedawk明显要快一些,但这里有两个展示一个例子,以及如何可以“曲线”的2种方法。

样本数据

对于以下示例,我们将使用以下数据

$ touch dir{1..3}/dir{100..112}/file{1..5}
$ touch dir{1..3}/dir{100..112}/nile{1..5}
$ touch dir{1..3}/dir{100..112}/knife{1..5}

*f*从中删除一些文件dir1/*

$ rm dir1/dir10{0..2}/*f*

方法#1-通过字符串解析

在这里,我们要使用以下工具,findgrep,和sort

$ find . -type f -name '*f*' | grep -o "\(.*\)/" | sort -u | head -5
./dir1/dir103/
./dir1/dir104/
./dir1/dir105/
./dir1/dir106/
./dir1/dir107/

方法2-使用文件进行解析

与以前相同的工具链,除了这次我们将使用dirname而不是grep

$ find . -type f -name '*f*' -exec dirname {} \; | sort -u | head -5
./dir1/dir103
./dir1/dir104
./dir1/dir105
./dir1/dir106
./dir1/dir107

注意:以上示例head -5仅用于限制我们为这些示例处理的输出量。通常会将其删除,以获取完整的列表!

比较结果

我们可以用来time看看这两种方法。

目录名

real        0m0.372s
user        0m0.028s
sys         0m0.106s

grep

real        0m0.012s
user        0m0.009s
sys         0m0.007s

因此,如果可能的话,最好总是处理字符串。

替代的字符串解析方法

grep和PCRE

$ find . -type f -name '*f*' | grep  -oP '^.*(?=/)' | sort -u

sed

$ find . -type f -name '*f*' | sed 's#/[^/]*$##' | sort -u

awk

$ find . -type f -name '*f*' | awk -F'/[^/]*$' '{print $1}' | sort -u

+1因为它可以工作,但是有趣的是,它花费的时间比@ John1024的答案长很多倍
Muhd

@Muhd-是的,对dirname的调用很慢。我正在研究替代方案。
slm

2

我发现这很有用:

find . -type f -name "*somefile*" | xargs dirname | sort | uniq

1

该答案基于slm答案是无耻的。这是一种有趣的方法,但是如果文件和/或目录名称具有特殊字符(空格,半列...),则存在局限性。一个好习惯是使用find /somewhere -print0 | xargs -0 someprogam

样本数据

对于以下示例,我们将使用以下数据

mkdir -p dir{1..3}/dir\ {100..112}
touch dir{1..3}/dir\ {100..112}/nile{1..5}
touch dir{1..3}/dir\ {100..112}/file{1..5}
touch dir{1..3}/dir\ {100..112}/kni\ fe{1..5}

*f*从中删除一些文件dir1/*/

rm dir1/dir\ 10{0..2}/*f*

方法#1-使用文件进行解析

$ find -type f -name '*f*' -print0 | sed -e 's#/[^/]*\x00#\x00#g' | sort -zu | xargs -0 -n1 echo | head -n5
./dir1/dir 103
./dir1/dir 104
./dir1/dir 105
./dir1/dir 106
./dir1/dir 107

注意:以上示例head -5仅用于限制我们为这些示例处理的输出量。通常会将其删除,以获取完整的列表!另外,替换echo您要使用的命令。


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.