不幸的是,他们俩都有自己的怪癖。
两者都是POSIX所必需的,因此它们之间的区别不是可移植性问题¹。
使用实用程序的简单方法是
base=$(basename -- "$filename")
dir=$(dirname -- "$filename")
请注意,与往常一样,请注意在变量替换周围加上双引号,并--
在命令后加上双引号,以防文件名以短划线开头(否则命令会将文件名解释为选项)。这种情况在少数情况下仍然会失败,这种情况很少见,但可能是由恶意用户²强制执行的:命令替换会删除尾随的换行符。因此,如果一个文件名叫做foo/bar
然后base
将被设置为bar
代替bar
。一种解决方法是添加一个非换行符,并在命令替换后将其删除:
base=$(basename -- "$filename"; echo .); base=${base%.}
dir=$(dirname -- "$filename"; echo .); dir=${dir%.}
使用参数替换,您不会遇到与扩展怪异字符有关的极端情况,但是斜杠字符存在许多困难。根本不是边缘情况的一件事是,对于没有目录的情况,计算目录部分需要不同的代码/
。
base="${filename##*/}"
case "$filename" in
*/*) dirname="${filename%/*}";;
*) dirname=".";;
esac
边缘情况是出现斜杠(包括根目录的情况,即所有斜杠)。该basename
和dirname
命令去掉结尾的斜杠做他们的工作才。如果您坚持使用POSIX结构,则无法一次性删除尾部的斜杠,但是您可以分两步进行。当输入仅包含斜杠时,您需要注意这种情况。
case "$filename" in
*/*[!/]*)
trail=${filename##*[!/]}; filename=${filename%%"$trail"}
base=${filename##*/}
dir=${filename%/*};;
*[!/]*)
trail=${filename##*[!/]}
base=${filename%%"$trail"}
dir=".";;
*) base="/"; dir="/";;
esac
如果您碰巧知道自己不在极端情况下(例如,find
除起点之外的其他结果始终包含目录部分且没有尾随/
),那么参数扩展字符串的操作就很简单。如果您需要处理所有极端情况,则实用程序更易于使用(但速度较慢)。
有时,您可能想要foo/
像foo/.
而不是那样对待foo
。如果您要作用于目录条目,则foo/
应该等效于foo/.
,而不是foo
; 当foo
到目录foo
的符号链接时,这会有所不同:表示符号链接,foo/
表示目标目录。在这种情况下,带斜杠的路径的基本名称最好是.
,并且该路径可以是其自己的目录名。
case "$filename" in
*/) base="."; dir="$filename";;
*/*) base="${filename##*/}"; dir="${filename%"$base"}";;
*) base="$filename"; dir=".";;
esac
快速可靠的方法是将zsh及其历史记录修饰符一起使用(这首先去除了斜杠,例如实用程序):
dir=$filename:h base=$filename:t
¹ 除非您使用的是Solaris 10及更早版本的POSIX之前的外壳/bin/sh
(在仍在生产中的机器上缺少参数扩展字符串操作功能-但是sh
安装中始终有一个POSIX外壳被调用,只有/usr/xpg4/bin/sh
,而不是/bin/sh
)。
² 例如:向foo
文件上传服务提交一个名为的文件,但不能防止这种情况的发生,然后将其删除并导致foo
被删除
base=$(basename -- "$filename"; echo .); base=${base%.}; dir=$(dirname -- "$filename"; echo .); dir=${dir%.}
?我正在仔细阅读,但没有注意到您提到任何缺点。