查找给定路径中的最后一个目录或文件

linuxopen sourceoperating system更新于 2026/1/27 19:52:17

概述

我们经常使用 Shell 脚本或 Linux 命令来处理路径。提取给定文件名的最后部分是一项相当常见的任务。

例如,如果我们尝试访问 /tmp/dir/target,那么我们希望能够将 target 作为文件名来访问。

是的,这看起来很容易。但可能存在一些极端情况会导致我们失败。

我们将仔细研究这个问题并探索一些常见的解决方案。

常见解决方案讨论

我们知道 Linux 文件系统不允许斜杠 (/) 成为文件名或目录的一部分。

因此,如果我们将输入路径字符串视为逗号分隔值的列表,那么我们可以简单地使用最后一个元素来解决问题。

我们可以使用各种命令来完成我们的任务,包括 grep(它可以帮助我们从文本文件中过滤掉某些行);awk(它允许我们操作文本文件);等等。

$ sed 's#.*/##' <<< "/tmp/dir/target"
target
$ awk -F'/' '{print $NF}' <<< "/tmp/dir/target"
target
$ grep -o '[^/]*$' <<< "/tmp/dir/target"
target

我们可以使用 Bash 的参数扩展来解决这个问题。

$ INPUT="/tmp/dir/target"
$ echo ${INPUT##*/}
target

可能还有很多其他类似的 CLI 工具,但它们真的足够稳定,可以用于生产环境吗?

如果您使用 /tmp/dir/target/,那么上述方法都将不起作用,因为它们会假定最后一个字符不是斜杠。

$ sed 's#.*/##' <<< "/tmp/dir/target/"
( empty output )
$ awk -F'/' '{print $NF}' <<< "/tmp/dir/target/"
( empty output )
$ grep -o '[^/]*$' <<< "/tmp/dir/target/"
( empty output )
$ INPUT="/tmp/dir/target/"
$ echo ${INPUT##*/}
( empty output )

我们可能需要修改上述解决方案,使其能够同时处理斜杠和反斜杠。例如,我们可以将 awk 解决方案修改为类似 −

$ awk -F'/' '{ a = length($NF) ? $NF : $(NF-1); print a }' <<< "/tmp/dir/target"
target
$ awk -F'/' '{ a = length($NF) ? $NF : $(NF-1); print a }' <<< "/tmp/dir/target/"
target

一个固定的 awk 单行命令可以用于大多数情况,但仍存在一些可能不起作用的极端情况。

现在让我们仔细研究一下。

探究极端情况

我们已经看到,Linux 文件系统可以用一组路径来表示。现在,我们将研究这些路径的其他一些可能模式。

首先,在 Linux 中,/ 是最顶层的目录。它包含所有其他目录和文件。因此,/ 是任何文件或目录的有效路径字符串。

此外,大多数 Linux 文件系统类型允许将空格用作文件名字符串或目录名的一部分。因此,如果文件或目录被""调用,它也是有效路径。

现在让我们看看 Linux 路径的所有可能模式,看看是否能得到正确的输出。

输入

预期输出

"/tmp/dir/target"

"target"

"/tmp/dir/target/"

"target"

"/"

"/"

"/tmp/dir/"

""

"/tmp/dir/ /"

" "

我们仍然可以扩展 awk 命令以涵盖所有情况,或者编写一个 bash 脚本来完成该任务。

我们将在这里使用 awks 单行命令作为示例 -

$ awk -F'/' '$0==FS{ print $0; next }{ a = length($NF) ? $NF : $(NF-1); print a }' <<< "/tmp/dir/target"
target
$ awk -F'/' '$0==FS{ print $0; next }{ a = length($NF) ? $NF : $(NF-1); print a }' <<< "/tmp/dir/target/"
target
$ awk -F'/' '$0==FS{ print $0; next }{ a = length($NF) ? $NF : $(NF-1); print a }' <<< "/"
/
$ echo "^$( awk -F'/' '$0==FS{ print $0; next }{ a = length($NF) ? $NF : $(NF-1); print a }' <<< "/tmp/dir/ " )\$"
^ $
$ echo "^$( awk -F'/' '$0==FS{ print $0; next }{ a = length($NF) ? $NF : $(NF-1); print a }' <<< "/tmp/dir/ /" )\$"
^ $

我们使用 ^ 和 $ 来指示预期结果的打印位置。

我们可以看到,awk 单行命令适用于所有情况,但与第一个版本(awk -F'/' '{printf "%s",$NF}')相比,它们现在相当复杂。

实际上,coreutils 包提供了一个方便的命令来解决我们的问题。

使用 basename 命令

basename 命令会从给定的路径字符串中剥离目录名称。

此外,它相当稳定,涵盖了所有极端情况。现在让我们用不同的输入值进行一些测试。

$ basename "/tmp/dir/target"
target
$ basename "/tmp/dir/target/"
target
$ basename "/"
/
$ echo "^$(basename '/tmp/dir/ ')\$"
^ $
$ echo "^$(basename '/tmp/dir/ /')\$"
^ $

basename 命令通过重命名文件解决了这个问题。

你可能想提一下,basename 命令(它会删除最后一个组件)有一个名为 dirnme 的兄弟命令(它会删除第一个组件)。

$ dirname "/tmp/dir/target"
/tmp/dir

如果我们想要处理路径,我们可以首先考虑 basename 和/或目录名是否可以解决我们的问题。通常,使用这两个命令的解决方案比较稳定,而且更容易阅读。

Awks 是强大的工具,但它们并不总是涵盖所有情况。如果你在脚本中使用它们,请注意不要忽略任何边缘情况。

结论

我们已经探讨了从路径字符串中提取最后一个组件的问题。

这个简单的问题有多种解决方案。我们找到了一个涵盖所有这些情况的 awk 单行代码。

我们还讨论了一种更简单的解决问题的方法:使用 basename 函数


相关文章


有用资源