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


概述

在处理路径时,我们经常使用 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 脚本。

我们在这里使用 awk 的单行命令作为示例:

$ 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 和/或目录名是否可以解决我们的问题。通常,使用这两个命令的解决方案是稳定的,并且更容易阅读。

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

结论

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

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

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

更新于: 2022-12-23

628 次查看

开启你的 职业生涯

通过完成课程获得认证

开始学习
广告