给定一个绝对路径或相对路径(在类unix系统中),我希望在解析任何中间符号链接之后确定目标的完整路径。同时还可以解析~用户名符号。

如果目标是一个目录,则可以将chdir()放入该目录,然后调用getcwd(),但我确实希望从shell脚本而不是编写C helper来完成此操作。不幸的是,shell倾向于试图向用户隐藏符号链接的存在(这是OS X上的bash):

$ ls -ld foo bar
drwxr-xr-x   2 greg  greg  68 Aug 11 22:36 bar
lrwxr-xr-x   1 greg  greg   3 Aug 11 22:36 foo -> bar
$ cd foo
$ pwd
/Users/greg/tmp/foo
$

我想要的是一个函数resolve(),这样当从上面的例子中的tmp目录执行时,resolve("foo") == "/Users/greg/tmp/bar"。


当前回答

readlink -f "$path"

编者注:上述内容适用于GNU readlink和FreeBSD/PC-BSD/OpenBSD readlink,但不适用于10.11版本的OS X。 GNU readlink提供了额外的相关选项,例如-m用于解析符号链接,无论最终目标是否存在。

注意,自从GNU coreutils 8.15(2012-01-06)以来,有一个realpath程序可用,它不那么迟钝,而且比上面的程序更灵活。它还兼容同名的FreeBSD util。它还包括在两个文件之间生成相对路径的功能。

realpath $path

[管理员添加以下评论由halloleo -danorton]

对于Mac OS X(至少10.11.x),使用不带-f选项的readlink:

readlink $path

编者注:这将不会递归地解析符号链接,因此不会报告最终目标;例如,给定指向b的符号链接a, b又指向c,这将只报告b(并且不会确保它作为绝对路径输出)。 在OS X上使用以下perl命令来填补缺少readlink -f功能的空白: perl -MCwd -le 'print Cwd::abs_path(shift)' "$path"

其他回答

在这里,我提出了一个我认为是跨平台(至少Linux和macOS)的解决方案,目前对我来说效果很好。

crosspath()
{
    local ref="$1"
    if [ -x "$(which realpath)" ]; then
        path="$(realpath "$ref")"
    else
        path="$(readlink -f "$ref" 2> /dev/null)"
        if [ $? -gt 0 ]; then
            if [ -x "$(which readlink)" ]; then
                if [ ! -z "$(readlink "$ref")" ]; then
                    ref="$(readlink "$ref")"
                fi
            else
                echo "realpath and readlink not available. The following may not be the final path." 1>&2
            fi
            if [ -d "$ref" ]; then
                path="$(cd "$ref"; pwd -P)"
            else
                path="$(cd $(dirname "$ref"); pwd -P)/$(basename "$ref")"
            fi
        fi
    fi
    echo "$path"
}

这里有一个macOS(仅?)解决方案。也许更适合最初的问题。

mac_realpath()
{
    local ref="$1"
    if [[ ! -z "$(readlink "$ref")" ]]; then
        ref="$(readlink "$1")"
    fi
    if [[ -d "$ref" ]]; then
        echo "$(cd "$ref"; pwd -P)"
    else
        echo "$(cd $(dirname "$ref"); pwd -P)/$(basename "$ref")"
    fi
}

如果你只是想要目录,“pwd -P”似乎可以工作,但如果出于某种原因你想要实际可执行文件的名称,我认为这没有帮助。以下是我的解决方案:

#!/bin/bash

# get the absolute path of the executable
SELF_PATH=$(cd -P -- "$(dirname -- "$0")" && pwd -P) && SELF_PATH=$SELF_PATH/$(basename -- "$0")

# resolve symlinks
while [[ -h $SELF_PATH ]]; do
    # 1) cd to directory of the symlink
    # 2) cd to the directory of where the symlink points
    # 3) get the pwd
    # 4) append the basename
    DIR=$(dirname -- "$SELF_PATH")
    SYM=$(readlink "$SELF_PATH")
    SELF_PATH=$(cd "$DIR" && cd "$(dirname -- "$SYM")" && pwd)/$(basename -- "$SYM")
done

在不能使用pwd的情况下(例如从不同的位置调用脚本),使用realpath(带dirname或不带dirname):

$(dirname $(realpath $PATH_TO_BE_RESOLVED))

工作时,通过(多个)符号链接(s)或直接调用脚本-从任何位置。

下面是如何使用内联Perl脚本在MacOS/Unix中获得文件的实际路径:

FILE=$(perl -e "use Cwd qw(abs_path); print abs_path('$0')")

类似地,要获取符号链接文件的目录:

DIR=$(perl -e "use Cwd qw(abs_path); use File::Basename; print dirname(abs_path('$0'))")

为了解决Mac不兼容的问题,我想到了

echo `php -r "echo realpath('foo');"`

不是很好,但是跨越了操作系统