从文件路径获取最后一个目录或文件名
1. 概述
当我们编写 shell 脚本或使用 Linux 命令行时,我们经常需要处理文件路径字符串。从给定的路径字符串中提取最后一个目录或文件名是一种非常常见的操作。
例如,对于给定的路径字符串“ /tmp/dir/target ”,我们尝试获取“ target ”作为结果。
是的,这看起来是一个非常简单的问题。当我们阅读上面的示例时,可能已经想到了几种解决方案。然而,这个简单的问题可能包括一些破坏我们解决方案的极端情况。
在本教程中,我们将仔细研究这个问题并评估常见的解决方案。
2. 共同解决方案的讨论
我们知道Linux 文件系统不允许斜杠 ( / ) 作为文件名或目录名的一部分。
因此,如果我们将输入路径字符串视为斜线分隔的值,我们可以只取最后一个值来解决问题。
如果我们看看我们的 Linux 命令库,许多强大的武器可能会帮助我们完成这项工作,例如*grep 、sed *和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
当然,使用其他命令行工具可能会有更多类似的解决方案。但是,它们真的是问题的稳定解决方案吗?
在 Linux 中,目录路径字符串通常以斜杠结尾,例如“ /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 one-liner 适用于 99% 的情况。但是,仍然存在可能破坏它的边缘情况。
接下来,让我们仔细看看它们。
3. 案例
在上一节中,我们学习了 Linux 路径字符串可以以斜杠结尾。现在,让我们看看路径字符串是否还有其他可能的模式。
首先,在 Linux 中,根目录是所有其他文件和目录的父目录。因此,根目录“ / ”是一个有效的路径字符串。
此外,大多数 Linux 文件系统允许空格作为文件名或目录名。因此,如果文件或目录以“”命名,它也是一个有效的路径字符串。
现在,让我们总结一下 Linux 路径字符串(输入)和我们预期的结果(输出)的所有可能模式:
输入 | 预期产出 |
---|---|
“ /tmp/dir/target” | “target” |
“ /tmp/dir/target/ ” | “target” |
“/“ | “/“ |
“ /tmp/dir/ ” | ” “ |
“ /tmp/dir/ / ” | ” “ |
如果我们愿意,我们仍然可以扩展 awk one-liner 以涵盖所有情况。同样,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’/’ ‘{print $NF}’)进行比较,它现在非常复杂。
实际上, Coreutils 包提供了一个方便的命令来解决我们的问题。
4. 使用basename命令
*顾名思义,*basename 命令可以去除给定路径字符串的父目录。
此外,它非常稳定,涵盖了所有极端情况。接下来,让我们用不同的输入做一个测试:
$ basename "/tmp/dir/target"
target
$ basename "/tmp/dir/target/"
target
$ basename "/"
/
$ echo "^$(basename '/tmp/dir/ ')\$"
^ $
$ echo "^$(basename '/tmp/dir/ /')\$"
^ $
如上面的输出所示, basename命令是该问题的直接解决方案。
值得一提的是basename命令有一个兄弟*dirname *,它的作用相反——从给定的路径字符串中删除最后一个组件:
$ dirname "/tmp/dir/target"
/tmp/dir
当我们需要处理路径字符串时,我们可以首先考虑basename和/或dirname是否可以解决问题。通常,使用这两个命令的解决方案是稳定且易于理解的。
awk是一个强大的实用程序,它当然可以解决问题。但是,我们必须考虑我们的awk实现是否涵盖了所有极端情况。否则,我们的解决方案可能会导致意想不到的结果——尤其是当它是脚本的一部分时。