我可以使用外部命令realpath
来获取文件的绝对路径:
realpath tmp/toto
回报
/home/john/tmp/toto
我可以使用bash
内置函数来获得相同的效果吗?
答案1
shellbash
没有内置方法来显式获取给定相对路径名的绝对路径。
在zsh
shell 中,人们可以做
pathname=../../home/kk/.zshenv
print -r -- $pathname:P
并返回/home/kk/.zshenv
(请注意,它还会尽可能以类似的方式解析所有符号链接realpath()
)。
在 中bash
,您可以对于指定非目录的路径名并假设您具有对其父目录的搜索访问权限,请使用
$ pathname=../../home/kk/.zshenv
$ ( OLDPWD=- CDPATH= cd -P -- "${pathname%/*}" && printf '%s/%s\n' "$PWD" "${pathname##*/}" )
/home/kk/.zshenv
也就是说,临时(在子 shell 中)cd
保存文件的目录,然后使用该值$PWD
和路径名的文件名部分构造绝对路径名。需要-P
避免cd
对..
元件进行特殊处理。它具有解析目录本身中的符号链接组件的副作用。但是,如果文件本身是符号链接,则不会解析它(这与zsh
s:P
修饰符不同)。
我们着手OLDPWD
解决-
这个问题,即cd -P -- -
使用 achdir($OLDPWD)
代替chdir("-")
(该技巧适用于bash
但不适用于所有其他sh
实现)。
对于目录路径来说更容易:
$ pathname=../../home/kk
$ ( OLDPWD=- CDPATH= cd -P -- "$pathname" && pwd )
/home/kk
为了方便起见,这显然可以放入 shell 函数中:
myrealpath () (
if [[ -d $1 ]]; then
OLDPWD=- CDPATH= cd -P -- "$1" && pwd
else
OLDPWD=- CDPATH= cd -P -- "${1%/*}" && printf '%s/%s\n' "$PWD" "${1##*/}"
fi
)
(请注意,整个函数是在子 shell 中定义的,以避免更改调用 shell 的工作目录。)
测试这个功能:
$ myrealpath ../../home/kk
/home/kk
$ myrealpath ../../home/kk/.zshenv
/home/kk/.zshenv
根据操作系统的不同,使用不带参数的函数将返回当前目录的绝对路径或给出错误。在 的未来版本中bash
,这可能是每个系统上的错误,因为cd ''
失败将成为 POSIX 要求。
答案2
Bash 带有一个realpath
可加载的扩展。由于 bash-4.4 应该默认安装,所以你应该能够执行以下操作:
BASH_LOADABLES_PATH=${BASH_LOADABLES_PATH:-/usr/local/lib/bash:/usr/lib/bash}
enable -f realpath realpath
realpath ..
help realpath
一旦启用,每个可加载命令就成为“真正的”内置命令,没有额外的 fork/exec 开销,并且能够操纵 bash 环境(如果提供,例如可mypid
加载)。
该-f
选项指定可加载扩展二进制文件的路径,如果它不在预期位置或者您的发行版没有正确设置它,您将需要将该变量设置BASH_LOADABLES_PATH
为包含扩展的目录(或者,-f
与可加载)。如果设置正确,则不需要上面的第一行。
当编写喜欢但不需要此类扩展的可移植脚本时,无论如何我都会使用包装函数 - 沿着 @Kusalananda 的路线 - 以便它可以回退到外部(例如readlink
或realpath
来自核心工具)。
- 此扩展自 bash-2.05 起可用,但在 bash-4.4 之前,默认情况下不会编译和安装扩展,尽管它是这样做很简单如果您有一个有效的构建环境(C 编译器等)。发行版可能包含也可能不包含扩展,遗憾的是 RHEL 8(以及 CentOS 8)构建 bash-4.4 时故意删除了可加载扩展。
答案3
我建议如下,这些很有可能在非 Linux 操作系统(包括 AIX)上工作。要计算绝对路径(即从 开始的路径/
):
abspath() {
[[ $1 = /* ]] && printf "%s\n" "$1" || printf "%s\n" "$PWD/$1"
}
对于规范路径(即所有符号链接均已展开的路径):
canonical_path() {
perl -mCwd -le ' print Cwd::abs_path $ARGV[0] ' -- "$1"
}
当针对其他可能的解决方案(包括对 的调用realpath
)进行测试时,您会注意到一个显着的差异,具体取决于最后一个路径组件是否存在以及最后一个路径组件是否是符号链接。
答案4
有些系统拥有“realpath”命令,例如Ubuntu。你可以尝试一下realpath ~/foo.txt
,它可能会给出类似的内容/home/user/foo.txt
,然后你可以使用basedir
它来截断文件名。