查找给定路径中的最后一个目录或文件
概述
我们经常使用 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 函数
相关文章
有用资源
linux 参考教程 - 该教程包含有关 linux 的更多信息:https://www.cainiaomax.com/linux/

