241

给定一个绝对或相对路径(在类 Unix 系统中),我想在解析任何中间符号链接后确定目标的完整路径。同时解决 ~username 符号的奖励积分。

如果目标是一个目录,则可以将 chdir() 放入该目录,然后调用 getcwd(),但我真的想从 shell 脚本中执行此操作,而不是编写 C 帮助程序。不幸的是,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"。

4

19 回答 19

436
readlink -f "$path"

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

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

realpath $path

[下面的管理员添加来自halloleo的评论—<a href="/users/65889/danorton">danorton]

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

readlink $path

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

于 2008-09-04T00:33:45.160 回答
99

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

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

获取 密码

于 2008-08-11T10:48:19.260 回答
28

如果您只想要目录,“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
于 2009-03-30T14:54:59.073 回答
22

我的最爱之一是realpath foo

realpath - 返回规范化的绝对​​路径名

realpath 扩展所有符号链接并解析对“/./”、“/../”和额外“/”字符的引用,这些字符在由 path 命名的空终止字符串中和
       将规范化的绝对​​路径名存储在由resolved_pa​​th 命名的大小为PATH_MAX 的缓冲区中。生成的路径将没有符号链接,“/./”或
       '/../' 组件。
于 2008-12-04T23:39:05.553 回答
10
readlink -e [filepath]

似乎正是您所要求的 - 它接受任意路径,解析所有符号链接,并返回“真实”路径 - 它是可能所有系统都已经拥有的“标准 *nix”

于 2015-07-09T14:34:38.377 回答
6

其他方式:

# Gets the real path of a link, following all links
myreadlink() { [ ! -h "$1" ] && echo "$1" || (local link="$(expr "$(command ls -ld -- "$1")" : '.*-> \(.*\)$')"; cd $(dirname $1); myreadlink "$link" | sed "s|^\([^/].*\)\$|$(dirname $1)/\1|"); }

# Returns the absolute path to a command, maybe in $PATH (which) or not. If not found, returns the same
whereis() { echo $1 | sed "s|^\([^/].*/.*\)|$(pwd)/\1|;s|^\([^/]*\)$|$(which -- $1)|;s|^$|$1|"; } 

# Returns the realpath of a called command.
whereis_realpath() { local SCRIPT_PATH=$(whereis $1); myreadlink ${SCRIPT_PATH} | sed "s|^\([^/].*\)\$|$(dirname ${SCRIPT_PATH})/\1|"; } 
于 2011-09-13T10:52:44.777 回答
5

将一些给定的解决方案放在一起,知道 readlink 在大多数系统上都可用,但需要不同的参数,这对我在 OSX 和 Debian 上很有效。我不确定 BSD 系统。也许条件只需要从 OSX[[ $OSTYPE != darwin* ]]中排除。-f

#!/bin/bash
MY_DIR=$( cd $(dirname $(readlink `[[ $OSTYPE == linux* ]] && echo "-f"` $0)) ; pwd -P)
echo "$MY_DIR"
于 2016-11-14T07:47:17.713 回答
4

以下是如何使用内联 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'))")
于 2018-01-30T03:57:48.270 回答
3

常见的 shell 脚本通常必须找到它们的“主”目录,即使它们被作为符号链接调用也是如此。因此,脚本必须从 $0 中找到它们的“真实”位置。

cat `mvn`

在我的系统上打印一个包含以下内容的脚本,这应该是您需要的一个很好的提示。

if [ -z "$M2_HOME" ] ; then
  ## resolve links - $0 may be a link to maven's home
  PRG="$0"

  # need this for relative symlinks
  while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
      PRG="$link"
    else
      PRG="`dirname "$PRG"`/$link"
    fi
  done

  saveddir=`pwd`

  M2_HOME=`dirname "$PRG"`/..

  # make it fully qualified
  M2_HOME=`cd "$M2_HOME" && pwd`
于 2008-08-11T11:17:23.803 回答
3

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

(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它,通过我第一次描述的简单技巧将它解析为一个真实的位置。

于 2018-06-28T18:00:47.407 回答
2
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"))/..
于 2014-04-10T14:52:57.620 回答
2

注意:我相信这是一个可靠的、便携的、现成的解决方案,正因为如此,它总是很冗长

下面是一个完全兼容 POSIX 的脚本/函数,因此它是跨平台的(也适用于 macOS,但从 10.12 (Sierra) 开始readlink仍不支持-f)-它仅使用POSIX shell 语言功能和仅与 POSIX 兼容的实用程序调用.

它是GNU (的更严格版本)的可移植实现readlink -ereadlink -f

您可以使用函数运行脚本sh或获取函数:bashkshzsh

例如,在脚本中,您可以按如下方式使用它来获取运行脚本的真实原始目录,并解析符号链接:

trueScriptDir=$(dirname -- "$(rreadlink "$0")")

rreadlink脚本/函数定义:

代码改编自这个答案的感激之情。
我还在这里bash创建了一个基于 - 的独立实用程序版本,如果您安装了 Node.js,您可以使用它进行安装。
npm install rreadlink -g

#!/bin/sh

# SYNOPSIS
#   rreadlink <fileOrDirPath>
# DESCRIPTION
#   Resolves <fileOrDirPath> to its ultimate target, if it is a symlink, and
#   prints its canonical path. If it is not a symlink, its own canonical path
#   is printed.
#   A broken symlink causes an error that reports the non-existent target.
# LIMITATIONS
#   - Won't work with filenames with embedded newlines or filenames containing 
#     the string ' -> '.
# COMPATIBILITY
#   This is a fully POSIX-compliant implementation of what GNU readlink's
#    -e option does.
# EXAMPLE
#   In a shell script, use the following to get that script's true directory of origin:
#     trueScriptDir=$(dirname -- "$(rreadlink "$0")")
rreadlink() ( # Execute the function in a *subshell* to localize variables and the effect of `cd`.

  target=$1 fname= targetDir= CDPATH=

  # Try to make the execution environment as predictable as possible:
  # All commands below are invoked via `command`, so we must make sure that
  # `command` itself is not redefined as an alias or shell function.
  # (Note that command is too inconsistent across shells, so we don't use it.)
  # `command` is a *builtin* in bash, dash, ksh, zsh, and some platforms do not 
  # even have an external utility version of it (e.g, Ubuntu).
  # `command` bypasses aliases and shell functions and also finds builtins 
  # in bash, dash, and ksh. In zsh, option POSIX_BUILTINS must be turned on for
  # that to happen.
  { \unalias command; \unset -f command; } >/dev/null 2>&1
  [ -n "$ZSH_VERSION" ] && options[POSIX_BUILTINS]=on # make zsh find *builtins* with `command` too.

  while :; do # Resolve potential symlinks until the ultimate target is found.
      [ -L "$target" ] || [ -e "$target" ] || { command printf '%s\n' "ERROR: '$target' does not exist." >&2; return 1; }
      command cd "$(command dirname -- "$target")" # Change to target dir; necessary for correct resolution of target path.
      fname=$(command basename -- "$target") # Extract filename.
      [ "$fname" = '/' ] && fname='' # !! curiously, `basename /` returns '/'
      if [ -L "$fname" ]; then
        # Extract [next] target path, which may be defined
        # *relative* to the symlink's own directory.
        # Note: We parse `ls -l` output to find the symlink target
        #       which is the only POSIX-compliant, albeit somewhat fragile, way.
        target=$(command ls -l "$fname")
        target=${target#* -> }
        continue # Resolve [next] symlink target.
      fi
      break # Ultimate target reached.
  done
  targetDir=$(command pwd -P) # Get canonical dir. path
  # Output the ultimate target's canonical path.
  # Note that we manually resolve paths ending in /. and /.. to make sure we have a normalized path.
  if [ "$fname" = '.' ]; then
    command printf '%s\n' "${targetDir%/}"
  elif  [ "$fname" = '..' ]; then
    # Caveat: something like /var/.. will resolve to /private (assuming /var@ -> /private/var), i.e. the '..' is applied
    # AFTER canonicalization.
    command printf '%s\n' "$(command dirname -- "${targetDir}")"
  else
    command printf '%s\n' "${targetDir%/}/$fname"
  fi
)

rreadlink "$@"

安全性的切线:

jarno参考确保 builtincommand不会被同名的别名或 shell 函数遮蔽的函数,在评论中询问:

如果unaliasorunset[被设置为别名或 shell 函数怎么办?

rreadlink确保其具有其原始含义的动机command是使用它绕过(良性)便利别名和函数,这些别名和函数通常用于隐藏交互式 shell 中的标准命令,例如重新定义ls以包含最喜欢的选项。

我认为可以肯定地说,除非您处理的是不受信任的恶意环境,否则不必担心unaliasunset- 或者,就此而言,while, do, ... - 被重新定义不是问题。

函数必须依赖某些东西才能具有其原始含义和行为 - 没有办法解决这个问题。
类似 POSIX 的 shell 允许重新定义内置函数甚至语言关键字,这本质上是一种安全风险(而且编写偏执的代码通常很困难)。

具体解决您的疑虑:

该功能依赖于unaliasunset具有其本来的含义。以改变其行为的方式将它们重新定义为shell 函数将是一个问题。重新定义为别名不一定是一个问题,因为引用(部分)命令名称(例如,\unalias)会绕过别名。

但是,引用不是shell关键字( while, for, if, do, ...) 的选项,虽然 shell 关键字确实优先于 shell函数,但 inbashzsh别名具有最高优先级,因此要防止 shell 关键字重新定义,您必须unalias运行它们的名称(尽管在非交互式 bashshell(例如脚本)中,别名默认情况下不会扩展 - 仅当shopt -s expand_aliases首先显式调用时)。

为了确保unalias- 作为内置 - 具有其原始含义,您必须首先使用\unset它,这需要unset具有其原始含义:

unset是一个 shell builtin,所以为了确保它被这样调用,你必须确保它本身没有被重新定义为一个function。虽然您可以通过引用绕过别名表单,但不能绕过 shell 函数表单 - catch 22。

因此,除非您可以依赖unset它的原始含义,否则据我所知,没有保证可以防御所有恶意重新定义。

于 2015-10-21T18:39:18.077 回答
2

这是 Bash 中的符号链接解析器,无论链接是目录还是非目录,它都可以工作:

function readlinks {(
  set -o errexit -o nounset
  declare n=0 limit=1024 link="$1"

  # If it's a directory, just skip all this.
  if cd "$link" 2>/dev/null
  then
    pwd -P
    return 0
  fi

  # Resolve until we are out of links (or recurse too deep).
  while [[ -L $link ]] && [[ $n -lt $limit ]]
  do
    cd "$(dirname -- "$link")"
    n=$((n + 1))
    link="$(readlink -- "${link##*/}")"
  done
  cd "$(dirname -- "$link")"

  if [[ $n -ge $limit ]]
  then
    echo "Recursion limit ($limit) exceeded." >&2
    return 2
  fi

  printf '%s/%s\n' "$(pwd -P)" "${link##*/}"
)}

请注意,所有的cdset东西都发生在一个子shell中。

于 2017-08-23T00:49:39.200 回答
2

如果不能使用 pwd(例如从不同位置调用脚本),请使用 realpath(带或不带 dirname):

$(dirname $(realpath $PATH_TO_BE_RESOLVED))

通过(多个)符号链接调用或直接调用脚本时都有效 - 从任何位置。

于 2020-02-19T21:01:22.317 回答
1

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

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

不是很好但跨操作系统

于 2014-08-08T08:50:18.657 回答
1

试试这个:

cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0))
于 2014-08-29T02:41:42.760 回答
1

由于这些年来我遇到过很多次,而这一次我需要一个可以在 OSX 和 linux 上使用的纯 bash 便携版本,我继续写了一个:

活版在这里:

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

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

让它适用于普通的 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- 路径:

%% 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 
于 2014-12-24T21:06:54.627 回答
0

在这里,我提出了一个我认为是跨平台(至少是 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
}
于 2019-01-14T10:32:08.340 回答
0

我在这里 Bash 的回答:如何获得符号链接的真实路径?

但简而言之,在脚本中非常方便:

script_home=$( dirname $(realpath "$0") )
echo Original script home: $script_home

这些是 GNU coreutils 的一部分,适用于 Linux 系统。

为了测试一切,我们将符号链接放入 /home/test2/,修改一些额外的东西并从根目录运行/调用它:

/$ /home/test2/symlink
/home/test
Original script home: /home/test

在哪里

Original script is: /home/test/realscript.sh
Called script is: /home/test2/symlink
于 2019-09-03T14:01:43.880 回答