428

在 Linux 上,该readlink实用程序接受-f附加链接后面的选项。这似乎不适用于 Mac 和可能基于 BSD 的系统。等价物是什么?

以下是一些调试信息:

$ which readlink; readlink -f
/usr/bin/readlink
readlink: illegal option -f
usage: readlink [-n] [file ...]
4

26 回答 26

476

MacPorts 和 Homebrew 提供了一个包含(GNU readlink)的coreutils包。greadlink感谢 Michael Kallweitt 在 mackb.com 上的帖子。

brew install coreutils

greadlink -f file.txt
于 2010-10-27T09:05:05.400 回答
198

readlink -f做两件事:

  1. 它沿着一系列符号链接迭代,直到找到一个实际的文件。
  2. 它返回该文件的规范化名称——即它的绝对路径名。

如果您愿意,您可以构建一个使用 vanilla 读取链接行为的 shell 脚本来实现相同的目的。这是一个例子。显然,您可以将其插入您自己想要调用的脚本中readlink -f

#!/bin/sh

TARGET_FILE=$1

cd `dirname $TARGET_FILE`
TARGET_FILE=`basename $TARGET_FILE`

# Iterate down a (possible) chain of symlinks
while [ -L "$TARGET_FILE" ]
do
    TARGET_FILE=`readlink $TARGET_FILE`
    cd `dirname $TARGET_FILE`
    TARGET_FILE=`basename $TARGET_FILE`
done

# Compute the canonicalized name by finding the physical path 
# for the directory we're in and appending the target file.
PHYS_DIR=`pwd -P`
RESULT=$PHYS_DIR/$TARGET_FILE
echo $RESULT

请注意,这不包括任何错误处理。特别重要的是,它不检测符号链接周期。一个简单的方法是计算你绕过循环的次数,如果你碰到一个不可能的大数字,比如 1,000,就会失败。

编辑使用pwd -P而不是$PWD.

请注意,如果您希望能够与类似 GNU readlink一起使用,该脚本预计将被称为 like ./script_name filename, no -f​​,更改$1为。$2-f filename

于 2009-07-12T20:51:37.750 回答
48

您可能对 .realpath(3)或 Python 的os.path.realpath. 两者并不完全相同。C 库调用要求存在中间路径组件,而 Python 版本不存在。

$ pwd
/tmp/foo
$ ls -l
total 16
-rw-r--r--  1 miles    wheel  0 Jul 11 21:08 a
lrwxr-xr-x  1 miles    wheel  1 Jul 11 20:49 b -> a
lrwxr-xr-x  1 miles    wheel  1 Jul 11 20:49 c -> b
$ python -c 'import os,sys;print(os.path.realpath(sys.argv[1]))' c
/private/tmp/foo/a

我知道你说过你更喜欢比另一种脚本语言更轻量的东西,但如果编译二进制文件令人难以忍受,你可以使用 Python 和 ctypes(在 Mac OS X 10.5 上可用)来包装库调用:

#!/usr/bin/python

import ctypes, sys

libc = ctypes.CDLL('libc.dylib')
libc.realpath.restype = ctypes.c_char_p
libc.__error.restype = ctypes.POINTER(ctypes.c_int)
libc.strerror.restype = ctypes.c_char_p

def realpath(path):
    buffer = ctypes.create_string_buffer(1024) # PATH_MAX
    if libc.realpath(path, buffer):
        return buffer.value
    else:
        errno = libc.__error().contents.value
        raise OSError(errno, "%s: %s" % (libc.strerror(errno), buffer.value))

if __name__ == '__main__':
    print realpath(sys.argv[1])

具有讽刺意味的是,这个脚本的 C 版本应该更短。:)

于 2009-07-12T01:11:34.190 回答
39

perl 中的一个简单的单行代码,几乎可以在任何地方工作,没有任何外部依赖:

perl -MCwd -e 'print Cwd::abs_path shift' ~/non-absolute/file

将取消引用符号链接。

脚本中的用法可能是这样的:

readlinkf(){ perl -MCwd -e 'print Cwd::abs_path shift' "$1";}
ABSPATH="$(readlinkf ./non-absolute/file)"
于 2014-07-04T10:28:22.643 回答
29

我不想继续使用另一种实现,但我需要 a)一个可移植的纯 shell 实现,和 b)单元测试覆盖率,因为这样的边缘情况的数量是不平凡的

查看我在 Github 上的项目以获取测试和完整代码。以下是实现的概要:

正如基思史密斯敏锐地指出的那样,readlink -f做了两件事:1)递归地解析符号链接,2)规范化结果,因此:

realpath() {
    canonicalize_path "$(resolve_symlinks "$1")"
}

首先,符号链接解析器实现:

resolve_symlinks() {
    local dir_context path
    path=$(readlink -- "$1")
    if [ $? -eq 0 ]; then
        dir_context=$(dirname -- "$1")
        resolve_symlinks "$(_prepend_path_if_relative "$dir_context" "$path")"
    else
        printf '%s\n' "$1"
    fi
}

_prepend_path_if_relative() {
    case "$2" in
        /* ) printf '%s\n' "$2" ;;
         * ) printf '%s\n' "$1/$2" ;;
    esac 
}

请注意,这是完整实现的略微简化版本。完整的实现添加了对符号链接周期的小检查,并稍微调整了输出。

最后,规范化路径的函数:

canonicalize_path() {
    if [ -d "$1" ]; then
        _canonicalize_dir_path "$1"
    else
        _canonicalize_file_path "$1"
    fi
}   

_canonicalize_dir_path() {
    (cd "$1" 2>/dev/null && pwd -P) 
}           

_canonicalize_file_path() {
    local dir file
    dir=$(dirname -- "$1")
    file=$(basename -- "$1")
    (cd "$dir" 2>/dev/null && printf '%s/%s\n' "$(pwd -P)" "$file")
}

就是这样,或多或少。简单到可以粘贴到您的脚本中,但又足够棘手,以至于您会疯狂地依赖任何没有针对您的用例进行单元测试的代码。

于 2014-04-09T18:34:58.820 回答
28
  1. 安装自制软件
  2. 运行“brew install coreutils”
  3. 运行“greadlink -f 路径”

greadlink 是实现 -f 的 gnu readlink。您也可以使用 macports 或其他,我更喜欢自制软件。

于 2012-03-29T02:35:28.667 回答
22

我亲自制作了一个名为 realpath 的脚本,它看起来有点像:

#!/usr/bin/env python
import os, sys
print(os.path.realpath(sys.argv[1]))
于 2009-11-05T06:05:40.377 回答
15

那这个呢?

function readlink() {
  DIR="${1%/*}"
  (cd "$DIR" && echo "$(pwd -P)")
}
于 2012-02-28T14:44:40.597 回答
11

一种适合我的懒惰方式,

$ brew install coreutils
$ ln -s /usr/local/bin/greadlink /usr/local/bin/readlink
$ which readlink
/usr/local/bin/readlink
/usr/bin/readlink
于 2020-01-27T21:11:27.130 回答
9

执行

  1. 安装冲泡

按照https://brew.sh/上的说明进行操作

  1. 安装 coreutils 包

brew install coreutils

  1. 创建别名或符号链接

3a。创建别名(每个用户)

您可以将别名放置在 ~/.bashrc、~/.bash_profile 或您习惯保留 bash 别名的任何位置。我个人将我的保存在 ~/.bashrc

alias readlink=greadlink

3b。创建符号链接(系统范围)

ln -s /usr/local/bin/greadlink /usr/local/bin/readlink(信用:伊扎娜)

这将在 /usr/local/bin 中创建一个符号链接,同时保持原始的 readlink 二进制文件完好无损。它之所以有效,是因为搜索 readlink 将返回 2 个结果。但是 /usr/local/bin 中的第二个将优先。

例如 which readlink

要撤消此更改,只需unlink /usr/local/bin/readlink

附加工具

您可以为其他 coreutils(如 gmv、gdu、gdf 等)创建类似的别名或符号链接。但是请注意,Mac 机器上的 GNU 行为可能会让习惯使用本机 coreutils 的其他人感到困惑,或者可能会在您的 mac 系统上以意想不到的方式表现。

解释

coreutils 是一个brew安装 GNU/Linux 核心实用程序的软件包,这些实用程序对应于它们的 Mac OSX 实现,以便您可以使用这些

您可能会在您的 mac osx 系统上找到看起来类似于 Linux coreutils(“核心实用程序”)的程序或实用程序,但它们在某些方面有所不同(例如具有不同的标志)。

这是因为这些工具的 Mac OSX 实现是不同的。要获得原始的类似 GNU/Linux 的行为,您可以coreutils通过brew包管理系统安装包。

这将安装相应的核心实用程序,前缀为g. 例如 for readlink,您会找到相应的greadlink程序。

为了使readlink执行类似于 GNU readlink( greadlink) 实现,您可以在安装 coreutils 后创建一个简单的别名或符号链接。

于 2019-05-20T11:22:30.047 回答
7

FreeBSDOSX都有stat派生自 NetBSD 的版本。

您可以使用格式开关调整输出(请参阅上面链接中的手册页)。

%  cd  /service
%  ls -tal 
drwxr-xr-x 22 root wheel 27 Aug 25 10:41 ..
drwx------  3 root wheel  8 Jun 30 13:59 .s6-svscan
drwxr-xr-x  3 root wheel  5 Jun 30 13:34 .
lrwxr-xr-x  1 root wheel 30 Dec 13 2013 clockspeed-adjust -> /var/service/clockspeed-adjust
lrwxr-xr-x  1 root wheel 29 Dec 13 2013 clockspeed-speed -> /var/service/clockspeed-speed
% stat -f%R  clockspeed-adjust
/var/service/clockspeed-adjust
% stat -f%Y  clockspeed-adjust
/var/service/clockspeed-adjust

某些 OS X 版本stat 可能缺少-f%R格式选项。在这种情况下-stat -f%Y可能就足够了。该-f%Y选项将显示符号链接的目标,而-f%R显示与文件对应的绝对路径名。

编辑:

如果您能够使用 Perl(Darwin/OS X 随最新版本一起安装perl),那么:

perl -MCwd=abs_path -le 'print abs_path readlink(shift);' linkedfile.txt

将工作。

于 2014-09-17T01:01:58.010 回答
7

解决此问题并在安装了 Homebrew 或 FreeBSD 的 Mac 上启用 readlink 功能的最简单方法是安装“coreutils”包。在某些 Linux 发行版和其他 POSIX 操作系统上也可能是必需的。

例如,在 FreeBSD 11 中,我通过调用安装:

# pkg install coreutils

在带有 Homebrew 的 MacOS 上,命令为:

$ brew install coreutils

不太清楚为什么其他答案如此复杂,仅此而已。这些文件不在其他位置,只是尚未安装。

于 2017-01-06T21:28:04.253 回答
6

这是一个可移植的 shell 函数,应该可以在任何Bourne 类似的 shell 中工作。它将解析相对路径标点“.. or .” 和取消引用符号链接。

如果由于某种原因您没有 realpath(1) 命令或 readlink(1),则可以使用别名。

which realpath || alias realpath='real_path'

享受:

real_path () {
  OIFS=$IFS
  IFS='/'
  for I in $1
  do
    # Resolve relative path punctuation.
    if [ "$I" = "." ] || [ -z "$I" ]
      then continue
    elif [ "$I" = ".." ]
      then FOO="${FOO%%/${FOO##*/}}"
           continue
      else FOO="${FOO}/${I}"
    fi

    ## Resolve symbolic links
    if [ -h "$FOO" ]
    then
    IFS=$OIFS
    set `ls -l "$FOO"`
    while shift ;
    do
      if [ "$1" = "->" ]
        then FOO=$2
             shift $#
             break
      fi
    done
    IFS='/'
    fi
  done
  IFS=$OIFS
  echo "$FOO"
}

另外,以防万一有人对这里感兴趣的是如何在 100% 纯 shell 代码中实现 basename 和 dirname:

## http://www.opengroup.org/onlinepubs/000095399/functions/dirname.html
# the dir name excludes the least portion behind the last slash.
dir_name () {
  echo "${1%/*}"
}

## http://www.opengroup.org/onlinepubs/000095399/functions/basename.html
# the base name excludes the greatest portion in front of the last slash.
base_name () {
  echo "${1##*/}"
}

你可以在我的谷歌网站上找到这个 shell 代码的更新版本:http ://sites.google.com/site/jdisnard/realpath

编辑:此代码根据 2 条款(freeBSD 风格)许可条款获得许可。通过上述指向我的网站的超链接,可以找到许可证的副本。

于 2010-04-26T00:10:54.297 回答
3

已经有很多答案,但没有一个对我有用......所以这就是我现在正在使用的。

readlink_f() {
  local target="$1"
  [ -f "$target" ] || return 1 #no nofile

  while [ -L "$target" ]; do
    target="$(readlink "$target")" 
  done
  echo "$(cd "$(dirname "$target")"; pwd -P)/$target"
}
于 2014-10-13T08:44:06.587 回答
3

开始更新

这是一个非常常见的问题,因此我们将一个名为realpath-lib的 Bash 4 库放在一起供免费使用(MIT 许可证) 。这旨在默认模拟readlink -f并包括两个测试套件来验证(1)它是否适用于给定的 unix 系统和(2)如果安装了readlink -f(但这不是必需的)。此外,它还可用于调查、识别和展开深层、损坏的符号链接和循环引用,因此它可以成为诊断深度嵌套的物理或符号目录和文件问题的有用工具。它可以在github.combitbucket.org上找到。

结束更新

另一个非常紧凑且高效的解决方案,除了 Bash 之外不依赖任何东西:

function get_realpath() {

    [[ ! -f "$1" ]] && return 1 # failure : file does not exist.
    [[ -n "$no_symlinks" ]] && local pwdp='pwd -P' || local pwdp='pwd' # do symlinks.
    echo "$( cd "$( echo "${1%/*}" )" 2>/dev/null; $pwdp )"/"${1##*/}" # echo result.
    return 0 # success

}

这还包括一个环境设置no_symlinks,它提供了将符号链接解析到物理系统的能力。只要no_symlinks设置为某些东西,即no_symlinks='on'符号链接将被解析到物理系统。否则将应用它们(默认设置)。

这应该适用于任何提供 Bash 的系统,并将返回与 Bash 兼容的退出代码以用于测试目的。

于 2013-10-08T14:43:46.387 回答
3

由于我的工作被非 BSD Linux 和 macOS 的人使用,我选择在我们的构建脚本中使用这些别名(sed包括在内,因为它有类似的问题):

##
# If you're running macOS, use homebrew to install greadlink/gsed first:
#   brew install coreutils
#
# Example use:
#   # Gets the directory of the currently running script
#   dotfilesDir=$(dirname "$(globalReadlink -fm "$0")")
#   alias al='pico ${dotfilesDir}/aliases.local'
##

function globalReadlink () {
  # Use greadlink if on macOS; otherwise use normal readlink
  if [[ $OSTYPE == darwin* ]]; then
    greadlink "$@"
  else
    readlink "$@"
  fi
}

function globalSed () {
  # Use gsed if on macOS; otherwise use normal sed
  if [[ $OSTYPE == darwin* ]]; then
    gsed "$@"
  else
    sed "$@"
  fi
}

您可以添加可选检查以自动安装homebrew + coreutils依赖项:

if [[ "$OSTYPE" == "darwin"* ]]; then
  # Install brew if needed
  if [ -z "$(which brew)" ]; then 
    /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"; 
  fi
  # Check for coreutils
  if [ -z "$(brew ls coreutils)" ]; then
    brew install coreutils
  fi
fi

我想要成为真正的“全球性”,它需要检查其他人……但这可能接近 80/20 大关。

于 2019-01-21T07:27:45.557 回答
2

POSIX shell 脚本的POSIX 兼容readlink -f实现

https://github.com/ko1nksm/readlinkf

这是符合 POSIX 的(没有 bashism)。它既不使用readlink也不使用realpath。我已经通过与 GNU 的比较验证了它是完全一样的readlink -f(见测试结果)。它具有错误处理和良好的性能。您可以安全地从readlink -f. 许可证为 CC0,因此您可以将其用于任何项目。

此代码在bats-core项目中采用。

# POSIX compliant version
readlinkf_posix() {
  [ "${1:-}" ] || return 1
  max_symlinks=40
  CDPATH='' # to avoid changing to an unexpected directory

  target=$1
  [ -e "${target%/}" ] || target=${1%"${1##*[!/]}"} # trim trailing slashes
  [ -d "${target:-/}" ] && target="$target/"

  cd -P . 2>/dev/null || return 1
  while [ "$max_symlinks" -ge 0 ] && max_symlinks=$((max_symlinks - 1)); do
    if [ ! "$target" = "${target%/*}" ]; then
      case $target in
        /*) cd -P "${target%/*}/" 2>/dev/null || break ;;
        *) cd -P "./${target%/*}" 2>/dev/null || break ;;
      esac
      target=${target##*/}
    fi

    if [ ! -L "$target" ]; then
      target="${PWD%/}${target:+/}${target}"
      printf '%s\n' "${target:-/}"
      return 0
    fi

    # `ls -dl` format: "%s %u %s %s %u %s %s -> %s\n",
    #   <file mode>, <number of links>, <owner name>, <group name>,
    #   <size>, <date and time>, <pathname of link>, <contents of link>
    # https://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html
    link=$(ls -dl -- "$target" 2>/dev/null) || break
    target=${link#*" $target -> "}
  done
  return 1
}

请参考最新的代码。它可能有些固定。

于 2020-03-07T17:14:02.830 回答
1

我想,迟到总比没有好。我之所以专门开发这个,是因为我的 Fedora 脚本不能在 Mac 上运行。问题是依赖项和 Bash。Mac 没有它们,或者如果有,它们通常在其他地方(另一条路径)。跨平台 Bash 脚本中的依赖路径操作充其量是一件令人头疼的事情,而在最坏的情况下则是一种安全风险——因此,如果可能的话,最好避免使用它们。

下面的函数 get_realpath() 很简单,以 Bash 为中心,不需要依赖。我只使用 Bash 内置echocd。它也相当安全,因为在每个阶段都会对所有内容进行测试并返回错误条件。

如果您不想遵循符号链接,则将set -P放在脚本的前面,否则cd应该默认解析符号链接。已使用 {absolute | 相对 | 符号链接 | local} 并返回文件的绝对路径。到目前为止,我们还没有遇到任何问题。

function get_realpath() {

if [[ -f "$1" ]]
then
    # file *must* exist
    if cd "$(echo "${1%/*}")" &>/dev/null
    then
        # file *may* not be local
        # exception is ./file.ext
        # try 'cd .; cd -;' *works!*
        local tmppwd="$PWD"
        cd - &>/dev/null
    else
        # file *must* be local
        local tmppwd="$PWD"
    fi
else
    # file *cannot* exist
    return 1 # failure
fi

# reassemble realpath
echo "$tmppwd"/"${1##*/}"
return 0 # success

}

您可以将它与其他函数 get_dirname、get_filename、get_stemname 和 validate_path 结合使用。这些可以在我们的 GitHub 存储库中作为realpath-lib找到(完全公开 - 这是我们的产品,但我们免费向社区提供,没有任何限制)。它也可以作为一种教学工具——它有据可查。

我们已尽最大努力应用所谓的“现代 Bash”实践,但 Bash 是一个很大的主题,我确信总会有改进的空间。它需要 Bash 4+,但如果它们仍然存在,则可以与旧版本一起使用。

于 2013-10-03T15:48:13.960 回答
1

echo $(cd $(dirname file1) ; pwd -P)

于 2022-01-06T08:58:14.180 回答
0

我为 OS X编写了一个 realpath 实用程序,它可以提供与readlink -f.


这是一个例子:

(jalcazar@mac tmp)$ ls -l a
lrwxrwxrwx 1 jalcazar jalcazar 11  8月 25 19:29 a -> /etc/passwd

(jalcazar@mac tmp)$ realpath a
/etc/passwd


如果您使用的是 MacPorts,则可以使用以下命令安装它:sudo port selfupdate && sudo port install realpath.

于 2014-11-08T14:55:02.573 回答
0

真正独立于平台的也是这个 R-onliner

readlink(){ RScript -e "cat(normalizePath(commandArgs(T)[1]))" "$1";}

要实际模仿readlink -f <path>,需要使用 $2 而不是 $1。

于 2015-12-10T20:17:01.780 回答
0

我只是将以下内容粘贴到我的 bash 脚本的顶部:

#!/usr/bin/env bash -e

declare script=$(basename "$0")
declare dirname=$(dirname "$0")
declare scriptDir
if [[ $(uname) == 'Linux' ]];then
    # use readlink -f
    scriptDir=$(readlink -f "$dirname")
else
    # can't use readlink -f, do a pwd -P in the script directory and then switch back
    if [[ "$dirname" = '.' ]];then
        # don't change directory, we are already inside
        scriptDir=$(pwd -P)
    else
        # switch to the directory and then switch back
        pwd=$(pwd)
        cd "$dirname"
        scriptDir=$(pwd -P)
        cd "$pwd"
    fi
fi

并删除了readlink -f. $scriptDir然后$script将可用于脚本的其余部分。

虽然这并不遵循所有符号链接,但它适用于所有系统并且对于大多数用例来说似乎足够好,它将目录切换到包含文件夹,然后它会pwd -P获取该目录的真实路径,然后最后切换回原来的。

于 2021-02-09T01:57:40.787 回答
-2

@Keith Smith 的回答给出了一个无限循环。

这是我的答案,我只在 SunOS 上使用(SunOS 错过了很多 POSIX 和 GNU 命令)。

这是一个脚本文件,您必须将其放入您的 $PATH 目录之一:

#!/bin/sh
! (($#)) && echo -e "ERROR: readlink <link to analyze>" 1>&2 && exit 99

link="$1"
while [ -L "$link" ]; do
  lastLink="$link"
  link=$(/bin/ls -ldq "$link")
  link="${link##* -> }"
  link=$(realpath "$link")
  [ "$link" == "$lastlink" ] && echo -e "ERROR: link loop detected on $link" 1>&2 && break
done

echo "$link"
于 2014-09-24T15:53:48.983 回答
-2

Perl 有一个 readlink 功能(例如,我如何在 Perl 中复制符号链接?)。这适用于大多数平台,包括 OS X:

perl -e "print readlink '/path/to/link'"

例如:

$ mkdir -p a/b/c
$ ln -s a/b/c x
$ perl -e "print readlink 'x'"
a/b/c
于 2011-08-27T11:59:18.560 回答
-3

这就是我使用的:

stat -f %N $your_path

于 2015-12-23T19:23:45.683 回答
-5

我的系统和您的系统之间的 readlink 路径不同。请尝试指定完整路径:

/sw/sbin/readlink -f

于 2009-06-30T13:46:02.577 回答