以前没有答案使用 bash 正则表达式
这是一个纯 bash ERE 解决方案,它将路径拆分为:
- 目录路径
/
,存在时带有尾随丢弃尾随的正则表达式要长得多,以至于我没有发布它
/
- 文件名,不包括(最后一个)点扩展名
- (最后一个)点扩展,其前导
.
该代码旨在处理所有可能的情况,欢迎您尝试。
#!/bin/bash
for path; do
####### the relevant part ######
[[ $path =~ ^(\.{1,2}|.*/\.{0,2})$|^(.*/)([^/]+)(\.[^/]*)$|^(.*/)(.+)$|^(.+)(\..*)$|^(.+)$ ]]
dirpath="${BASH_REMATCH[1]}${BASH_REMATCH[2]}${BASH_REMATCH[5]}"
filename="${BASH_REMATCH[3]}${BASH_REMATCH[6]}${BASH_REMATCH[7]}${BASH_REMATCH[9]}"
filext="${BASH_REMATCH[4]}${BASH_REMATCH[8]}"
# dirpath should be non-null
[[ $dirpath ]] || dirpath='.'
################################
printf '%s=%q\n' \
path "$path" \
dirpath "$dirpath" \
filename "$filename" \
filext "$filext"
done
它是如何工作的?
基本上,它确保只有一个子表达式(|
在正则表达式中分隔)能够捕获输入。多亏了这一点,您可以毫无顾虑地连接所有相同类型的捕获组(例如,与目录路径相关的捕获组),BASH_REMATCH
因为最多一个将是非空的。
以下是一组扩展但并不详尽的示例的结果:
+--------------------------------------------------------+
| input dirpath filename filext |
+--------------------------------------------------------+
'' . '' ''
. . '' ''
.. .. '' ''
... . .. .
.file . .file ''
.file. . .file .
.file.. . .file. .
.file.Z . .file .Z
.file.sh.Z . .file.sh .Z
file . file ''
file. . file .
file.. . file. .
file.Z . file .Z
file.sh.Z . file.sh .Z
dir/ dir/ '' ''
dir/. dir/. '' ''
dir/... dir/ .. .
dir/.file dir/ .file ''
dir/.file. dir/ .file .
dir/.file.. dir/ .file. .
dir/.file.Z dir/ .file .Z
dir/.file.x.Z dir/ .file.x .Z
dir/file dir/ file ''
dir/file. dir/ file .
dir/file.. dir/ file. .
dir/file.Z dir/ file .Z
dir/file.x.Z dir/ file.x .Z
dir./. dir./. '' ''
dir./... dir./ .. .
dir./.file dir./ .file ''
dir./.file. dir./ .file .
dir./.file.. dir./ .file. .
dir./.file.Z dir./ .file .Z
dir./.file.sh.Z dir./ .file.sh .Z
dir./file dir./ file ''
dir./file. dir./ file .
dir./file.. dir./ file. .
dir./file.Z dir./ file .Z
dir./file.x.Z dir./ file.x .Z
dir// dir// '' ''
dir//. dir//. '' ''
dir//... dir// .. .
dir//.file dir// .file ''
dir//.file. dir// .file .
dir//.file.. dir// .file. .
dir//.file.Z dir// .file .Z
dir//.file.x.Z dir// .file.x .Z
dir//file dir// file ''
dir//file. dir// file .
dir//file.. dir// file. .
dir//file.Z dir// file .Z
dir//file.x.Z dir// file.x .Z
dir.//. dir.//. '' ''
dir.//... dir.// .. .
dir.//.file dir.// .file ''
dir.//.file. dir.// .file .
dir.//.file.. dir.// .file. .
dir.//.file.Z dir.// .file .Z
dir.//.file.x.Z dir.// .file.x .Z
dir.//file dir.// file ''
dir.//file. dir.// file .
dir.//file.. dir.// file. .
dir.//file.Z dir.// file .Z
dir.//file.x.Z dir.// file.x .Z
/ / '' ''
/. /. '' ''
/.. /.. '' ''
/... / .. .
/.file / .file ''
/.file. / .file .
/.file.. / .file. .
/.file.Z / .file .Z
/.file.sh.Z / .file.sh .Z
/file / file ''
/file. / file .
/file.. / file. .
/file.Z / file .Z
/file.sh.Z / file.sh .Z
/dir/ /dir/ '' ''
/dir/. /dir/. '' ''
/dir/... /dir/ .. .
/dir/.file /dir/ .file ''
/dir/.file. /dir/ .file .
/dir/.file.. /dir/ .file. .
/dir/.file.Z /dir/ .file .Z
/dir/.file.x.Z /dir/ .file.x .Z
/dir/file /dir/ file ''
/dir/file. /dir/ file .
/dir/file.. /dir/ file. .
/dir/file.Z /dir/ file .Z
/dir/file.x.Z /dir/ file.x .Z
/dir./. /dir./. '' ''
/dir./... /dir./ .. .
/dir./.file /dir./ .file ''
/dir./.file. /dir./ .file .
/dir./.file.. /dir./ .file. .
/dir./.file.Z /dir./ .file .Z
/dir./.file.sh.Z /dir./ .file.sh .Z
/dir./file /dir./ file ''
/dir./file. /dir./ file .
/dir./file.. /dir./ file. .
/dir./file.Z /dir./ file .Z
/dir./file.x.Z /dir./ file.x .Z
/dir// /dir// '' ''
/dir//. /dir//. '' ''
/dir//... /dir// .. .
/dir//.file /dir// .file ''
/dir//.file. /dir// .file .
/dir//.file.. /dir// .file. .
/dir//.file.Z /dir// .file .Z
/dir//.file.x.Z /dir// .file.x .Z
/dir//file /dir// file ''
/dir//file. /dir// file .
/dir//file.. /dir// file. .
/dir//file.Z /dir// file .Z
/dir//file.x.Z /dir// file.x .Z
/dir.//. /dir.//. '' ''
/dir.//... /dir.// .. .
/dir.//.file /dir.// .file ''
/dir.//.file. /dir.// .file .
/dir.//.file.. /dir.// .file. .
/dir.//.file.Z /dir.// .file .Z
/dir.//.file.x.Z /dir.// .file.x .Z
/dir.//file /dir.// file ''
/dir.//file. /dir.// file .
/dir.//file.. /dir.// file. .
/dir.//file.Z /dir.// file .Z
/dir.//file.x.Z /dir.// file.x .Z
// // '' ''
//. //. '' ''
//.. //.. '' ''
//... // .. .
//.file // .file ''
//.file. // .file .
//.file.. // .file. .
//.file.Z // .file .Z
//.file.sh.Z // .file.sh .Z
//file // file ''
//file. // file .
//file.. // file. .
//file.Z // file .Z
//file.sh.Z // file.sh .Z
//dir/ //dir/ '' ''
//dir/. //dir/. '' ''
//dir/... //dir/ .. .
//dir/.file //dir/ .file ''
//dir/.file. //dir/ .file .
//dir/.file.. //dir/ .file. .
//dir/.file.Z //dir/ .file .Z
//dir/.file.x.Z //dir/ .file.x .Z
//dir/file //dir/ file ''
//dir/file. //dir/ file .
//dir/file.. //dir/ file. .
//dir/file.Z //dir/ file .Z
//dir/file.x.Z //dir/ file.x .Z
//dir./. //dir./. '' ''
//dir./... //dir./ .. .
//dir./.file //dir./ .file ''
//dir./.file. //dir./ .file .
//dir./.file.. //dir./ .file. .
//dir./.file.Z //dir./ .file .Z
//dir./.file.sh.Z //dir./ .file.sh .Z
//dir./file //dir./ file ''
//dir./file. //dir./ file .
//dir./file.. //dir./ file. .
//dir./file.Z //dir./ file .Z
//dir./file.x.Z //dir./ file.x .Z
//dir// //dir// '' ''
//dir//. //dir//. '' ''
//dir//... //dir// .. .
//dir//.file //dir// .file ''
//dir//.file. //dir// .file .
//dir//.file.. //dir// .file. .
//dir//.file.Z //dir// .file .Z
//dir//.file.x.Z //dir// .file.x .Z
//dir//file //dir// file ''
//dir//file. //dir// file .
//dir//file.. //dir// file. .
//dir//file.Z //dir// file .Z
//dir//file.x.Z //dir// file.x .Z
//dir.//. //dir.//. '' ''
//dir.//... //dir.// .. .
//dir.//.file //dir.// .file ''
//dir.//.file. //dir.// .file .
//dir.//.file.. //dir.// .file. .
//dir.//.file.Z //dir.// .file .Z
//dir.//.file.x.Z //dir.// .file.x .Z
//dir.//file //dir.// file ''
//dir.//file. //dir.// file .
//dir.//file.. //dir.// file. .
//dir.//file.Z //dir.// file .Z
//dir.//file.x.Z //dir.// file.x .Z
如您所见,该行为与basename
和不同dirname
。例如basename dir/
输出dir
,而正则表达式将为您提供一个空文件名。.
和相同..
,它们被视为目录,而不是文件名。
我用 256 个字符的 10000 条路径对其进行计时,大约需要 1 秒,而等效的 POSIX shell 解决方案要慢 2 倍,而基于野生分叉(for
循环内的外部调用)的解决方案至少要慢 60 倍。
备注:没有必要测试包含\n
或其他臭名昭著字符的路径,因为所有字符都由 bash 的正则表达式引擎以相同的方式处理。唯一能够打破当前逻辑的字符是/
and ,以当前意想不到的方式.
混合或相乘。当我第一次发布我的答案时,我发现了一些我必须修复的边界案例;我不能说正则表达式是 100% 防弹的,但它现在应该非常健壮。
顺便说一句,这是产生相同输出的纯 POSIX shell 解决方案:
#!/bin/sh
for path; do
####### the relevant part ######
basename=${path##*/}
case $basename in
. | ..)
dirpath="$path"
filename=''
filext=''
basename=''
;;
*)
dirpath=${path%"$basename"}
filename=${basename#.}
filename="${basename%"$filename"}${filename%.*}"
filext=${basename#"$filename"}
esac
# dirpath should be non-null
[ -z "$dirpath" ] && dirpath='.'
################################
printf '%s=%s\n' \
path "$filepath" \
dirpath "$dirpath" \
filename "$filename" \
filext "$filext"
done
后记:有几点可能有人不同意上述代码给出的结果:
dotfiles的特殊情况:原因是dotfiles 是一个 UNIX 概念。
.
and的特殊情况..
:恕我直言,将它们视为目录似乎很明显,但大多数库都没有,并强制用户对结果进行后处理。
不支持双扩展:这是因为您需要一个完整的数据库来存储所有有效的双扩展,最重要的是,因为文件扩展在 UNIX 中没有任何意义;例如,您可以调用 tar 存档my_tarred_files
,这完全没问题,您可以tar xf my_tarred_files
毫无问题地使用。