我有一个文件夹列表,我想知道大多数文件夹名称以哪个文件夹名称开头。
鉴于此文件夹列表:
./
./.something
./unrelated
./Target.Dire ctoryName
./Target.Dire ctoryNameOther
./Target.Dire ctoryName.Other
./Target.Dire ctoryName.Stuff
./Target.Dire ctoryName.Stuff.Other
./Unrelated.Dire ctoryName
./Unrelated.Dire ctoryNameStuff
./Unrelated.Dire ctoryName.Stuff
./Unrelated.Dire ctoryName.Other
./More.Unrelated.Dire ctoryName
./More.Unrelated.Dire ctoryName.Other
我想知道./Target.Dire ctoryName
(包括空格)的名称。
我想出了这个,但它排除了隐藏文件夹并且非常长,而且也不执行“开头为”而是执行“包含”:
find * -maxdepth 0 -type d | xargs -d $'\n' sh -c \
'for arg do find * -maxdepth 0 -type d | grep -wo "$arg"; done' _ \
| sort | uniq -c | sort -nr | head -1 | echo "./$(awk '{print $2}')"
可能有两种不同的版本,一种包含隐藏文件夹,另一种不包含隐藏文件夹。对于我的用例,find * -maxdepth 0 -type d
包括所有潜在的文件夹。
为什么我需要文件夹名称是因为我使用的工具在与特定参数一起使用时无法找出“目标/主”目录。
答案1
使用zsh
,您可以执行以下操作:
all=(*(N/)) max=0 best=
for dir ($all) (){ (( $# > max )) && max=$# best=$dir; } ${(M)all:#$dir*}
print -r -- $max $best
其中*(N/)
扩展到类型的非隐藏文件目录在当前目录中(或者如果与 ullglob 限定符不匹配则什么也没有N
)。
${(M)all:#$dir*}
扩展到匹配模式的$all
数组元素,即以.这些值作为参数传递给匿名函数 ( ),该函数将其与最大值进行比较。M
$dir*
$dir
(){....}
如果有相同的情况,则选择按词法排序的目录列表中的第一个目录(替换>
为>=
以获得最后一个目录)。
添加D
(for dotglob
) glob 限定符 ( all=(*(ND/))
) 以包含隐藏目录(.
并且..
始终排除)。
以下是我原来的答案,我太快地阅读了要求。这会找到最常见的那些目录的公共前缀,即使它不是目录本身的名称。例如,在ab1
、ab2
ab3
和bc
之间bc1
,它将返回ab
而不是bc
:
typeset -A c
for f (*(/)) for ((i=1;i<=$#f;i++)) ((c[\$f[1,i]]++))
printf -v argv '%2$08d%1$s' ${(kv)c}
print -r -- ${${(O)@}[1][9,-1]}
其中*(/)
扩展到类型的非隐藏文件目录在当前目录中(如果没有则中止并出现错误)。
然后我们构建$c
关联数组来记录这些目录名称的每个可能前缀的出现次数。
然后,我们根据出现次数用零填充到 8 位数字构造$argv
(又名)数组,后跟每个前缀的前缀。$@
然后我们O
按词法反向对该数组进行排序,因此第一个数组是出现次数最多的数组,如果存在平局,则排序最后的数组(这也将给出最长的前缀)。
然后我们选择第一个 ( ),并打印它的[1]
倒数第 9 个字符 ( )。[9,-1]
要包含隐藏目录,请替换*(/)
为*(D/)
(添加D
fordotglob
限定符)。.
并且..
从未被包括在内。
答案2
我不确定如何在纯 bash 中完成此操作,但下面的 Python 脚本应该适用于所有可能的文件夹名称。它还接受命令行参数(稍后将在下面描述)可选的模式匹配参数)。
#!/usr/bin/env python3
import subprocess
import sys
if len(sys.argv) == 1:
folder = subprocess.run(\
['find','-mindepth','1','-maxdepth','1','-type','d','-print0'],\
capture_output=True).stdout[:-1].split(b'\x00')
else:
folder = subprocess.run(\
['find','-mindepth','1','-maxdepth','1','-type','d',\
'-name',sys.argv[1], '-print0'],\
capture_output=True).stdout[:-1].split(b'\x00')
counts = []
for name in folder:
counts.append(sum(name in i[:len(name)] for i in folder))
counts = sorted(list(zip(counts,folder)),reverse=True)
if counts[0][0]>1:
sys.stdout.buffer.write(counts[0][1]+b'\x0a')
保存脚本(例如 asmainfoldername.py
或其他),命令python3 /path/to/mainfoldername.py
将输出满足最常出现标准的文件夹名称,作为当前工作目录中文件夹名称的起始字符串。如果没有文件夹满足该条件,则脚本不会产生输出。
使用您给出的示例目录,python3 /path/to/mainfoldername.py
将输出./Target.Dire ctoryName
。
使用命令扩展,可以将变量设置为脚本的输出值并在其他命令中使用:
$ myvar="$( python3 /path/to/mainfoldername.py )"
$ echo "$myvar"
./Target.Dire ctoryName
$ cd "$myvar"
Target.Dire ctoryName$
shell脚本的使用
在外壳脚本将使用此输出,应通过测试零长度值来检查是否没有文件夹名称符合条件(即 python 脚本未生成输出),例如:
#!/bin/sh
myvar="$( python3 /path/to/mainfoldername.py )"
[ ! -z "$myvar" ] || exit 1
可选的模式匹配参数
python3 /path/to/mainfolder.py PATTERN
该脚本将在命令行上接受一个可选参数,其中PATTERN
是文件夹名称必须匹配的 shell 模式。
其最基本的用途是在查找常规文件夹或查找隐藏文件夹之间进行切换。该脚本的默认行为是查找所有文件夹。
仅查找隐藏文件夹, 使用'.*'
为了PATTERN
:
python3 /path/to/mainfolder.py '.*'
仅查找非隐藏文件夹, 使用'[!\.]*'
python3 /path/to/mainfolder.py '[!\.]*'
当您知道目标文件夹名称中将出现的某些字符串时,它也很有用。
仅查找名称包含该字符串的文件夹Target
:
python3 /path/to/mainfolder.py '*Target*'
请注意,模式应始终包含在引号中,以避免 shell 进行扩展。看《贝壳图案匹配》了解更多信息。
答案3
以下awk
程序将输出最常出现的目录路径作为输入中所有目录路径的前缀。
假设目录路径一次显示一行,并且没有路径名包含嵌入的换行符。
{ sub("/$",""); count[$0] = 0 }
END {
for (p1 in count)
for (p2 in count) {
count[p2] += (index(p1,p2) == 1)
if (count[p2] > m) {
m = count[p2]
p = p2
}
}
print p
}
这会读取每一行输入并将它们作为键存储在关联数组中count
。/
首先删除任何尾随。
当所有输入都被读取后,存储的键会成对进行比较,并计算每个键作为子字符串在另一个键中出现的次数。我们跟踪在 中出现次数最多的路径名p
,以及它在 中出现的次数m
。
该调用index(p1,p2)
返回字符串中出现的位置p1
(p2
如果根本没有出现则返回零),我们只对结果为数字 1 的情况感兴趣(p2
出现在 的开头p1
)。
上述程序也可以在命令行上内联编写,如下所示:
awk '{ sub("/$",""); c[$0]=0 } END { for (a in c) for (b in c) { c[b]+=(index(a,b)==1); if (c[b]>m) { m=c[b]; p=b } } print p }'
作为对此的输入,您可以使用
printf '%s\n' ./*/
(dotglob
在bash
shell 中启用还可以获取隐藏目录名称。)
最后,你会得到类似的东西
shopt -s dotglob
printf '%s\n' ./*/ | awk -f script
(或使用较长的内联awk
脚本代替-f script
。)
测试:
$ ls -FA
.something/ Unrelated.Dire ctoryName/
Target.Dire ctoryName/ Unrelated.Dire ctoryName.Other/
Target.Dire ctoryName.Other/ Unrelated.Dire ctoryName.Stuff/
Target.Dire ctoryName.Stuff/ Unrelated.Dire ctoryNameStuff/
Target.Dire ctoryName.Stuff.Other/ script
Target.Dire ctoryNameOther/ unrelated/
$ shopt -s dotglob
$ printf '%s\n' ./*/ | awk -f script
./Target.Dire ctoryName
答案4
C# 脚本
这是一个C# 脚本:
#!/usr/bin/env dotnet-script
var dirs = Directory.GetDirectories(".");
Console.Write(dirs.MaxBy(d => dirs.Count(x => x.StartsWith(d))));
在目录中,运行dotnet script /path/to/mainfoldername.csx
.