给定一个绝对路径或相对路径(在类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"。


当前回答

您的路径是目录还是文件?如果它是一个目录,它很简单:

(cd "$DIR"; pwd -P)

然而,如果它可能是一个文件,那么这将不起作用:

DIR=$(cd $(dirname "$FILE"); pwd -P); echo "${DIR}/$(readlink "$FILE")"

因为符号链接可能解析为相对路径或完整路径。

在脚本上,我需要找到真正的路径,这样我就可以引用配置或其他脚本安装在一起,我使用这个:

SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
  SOURCE="$(readlink "$SOURCE")"
  [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done

您可以将SOURCE设置为任何文件路径。基本上,只要路径是符号链接,它就解析那个符号链接。诀窍在循环的最后一行。如果解析的符号链接是绝对的,它将使用它作为SOURCE。然而,如果它是相对的,它将在DIR之前为它,通过我首先描述的简单技巧将其解析为一个真实位置。

其他回答

function realpath {
    local r=$1; local t=$(readlink $r)
    while [ $t ]; do
        r=$(cd $(dirname $r) && cd $(dirname $t) && pwd -P)/$(basename $t)
        t=$(readlink $r)
    done
    echo $r
}

#example usage
SCRIPT_PARENT_DIR=$(dirname $(realpath "$0"))/..

根据标准,pwd -P应该返回已解析符号链接的路径。

C函数char *getcwd(char *buf, size_t size)从unistd.h应该有相同的行为。

getcwd 松材线虫病

试试这个:

cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0))

因为我在过去的几年里遇到过很多次这样的情况,这一次我需要一个纯粹的bash可移植版本,可以在OSX和linux上使用,我继续写了一个:

活生生的版本就在这里:

https://github.com/keen99/shell-functions/tree/master/resolve_path

但为了SO,这里是当前的版本(我觉得它经过了很好的测试..但我愿意接受反馈!)

可能不难使它为普通bourne shell (sh)工作,但我没有尝试…我太喜欢$FUNCNAME了。:)

#!/bin/bash

resolve_path() {
    #I'm bash only, please!
    # usage:  resolve_path <a file or directory> 
    # follows symlinks and relative paths, returns a full real path
    #
    local owd="$PWD"
    #echo "$FUNCNAME for $1" >&2
    local opath="$1"
    local npath=""
    local obase=$(basename "$opath")
    local odir=$(dirname "$opath")
    if [[ -L "$opath" ]]
    then
    #it's a link.
    #file or directory, we want to cd into it's dir
        cd $odir
    #then extract where the link points.
        npath=$(readlink "$obase")
        #have to -L BEFORE we -f, because -f includes -L :(
        if [[ -L $npath ]]
         then
        #the link points to another symlink, so go follow that.
            resolve_path "$npath"
            #and finish out early, we're done.
            return $?
            #done
        elif [[ -f $npath ]]
        #the link points to a file.
         then
            #get the dir for the new file
            nbase=$(basename $npath)
            npath=$(dirname $npath)
            cd "$npath"
            ndir=$(pwd -P)
            retval=0
            #done
        elif [[ -d $npath ]]
         then
        #the link points to a directory.
            cd "$npath"
            ndir=$(pwd -P)
            retval=0
            #done
        else
            echo "$FUNCNAME: ERROR: unknown condition inside link!!" >&2
            echo "opath [[ $opath ]]" >&2
            echo "npath [[ $npath ]]" >&2
            return 1
        fi
    else
        if ! [[ -e "$opath" ]]
         then
            echo "$FUNCNAME: $opath: No such file or directory" >&2
            return 1
            #and break early
        elif [[ -d "$opath" ]]
         then 
            cd "$opath"
            ndir=$(pwd -P)
            retval=0
            #done
        elif [[ -f "$opath" ]]
         then
            cd $odir
            ndir=$(pwd -P)
            nbase=$(basename "$opath")
            retval=0
            #done
        else
            echo "$FUNCNAME: ERROR: unknown condition outside link!!" >&2
            echo "opath [[ $opath ]]" >&2
            return 1
        fi
    fi
    #now assemble our output
    echo -n "$ndir"
    if [[ "x${nbase:=}" != "x" ]]
     then
        echo "/$nbase"
    else 
        echo
    fi
    #now return to where we were
    cd "$owd"
    return $retval
}

这里有一个经典的例子,感谢brew:

%% ls -l `which mvn`
lrwxr-xr-x  1 draistrick  502  29 Dec 17 10:50 /usr/local/bin/mvn@ -> ../Cellar/maven/3.2.3/bin/mvn

使用这个函数,它将返回-real- path:

%% cat test.sh
#!/bin/bash
. resolve_path.inc
echo
echo "relative symlinked path:"
which mvn
echo
echo "and the real path:"
resolve_path `which mvn`


%% test.sh

relative symlinked path:
/usr/local/bin/mvn

and the real path:
/usr/local/Cellar/maven/3.2.3/libexec/bin/mvn 
readlink -e [filepath]

看来这正是你想要的 -它接受任意路径,解析所有符号链接,并返回“真实”路径 -可能所有系统都已经有了“标准*nix”