从给定路径中查找最后一个目录或文件
概述
在处理路径时,我们经常使用 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 函数