macos - 如何在Mac上获取GNU的readlink -f的行为?

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

这是一些调试信息:

$ which readlink; readlink -f
/usr/bin/readlink
readlink: illegal option -f
usage: readlink [-n] [file ...]
troelskn asked 2019-02-06T06:52:14Z
20个解决方案
356 votes

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

brew install coreutils

greadlink -f file.txt
tomyjwu answered 2019-02-06T06:53:29Z
142 votes

./script_name filename做两件事:

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

如果您愿意,您可以构建一个使用vanilla readlink行为的shell脚本来实现相同的功能。 这是一个例子。 显然你可以在自己的脚本中插入这个,你想打电话给./script_name filename

#!/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

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

EDITED使用./script_name filename而不是-f

请注意,如果您希望能够使用像GNU readlink这样的-f filename,则此脚本可能会被调用为./script_name filename,无-f,更改为$1$2

Keith Smith answered 2019-02-06T06:53:06Z
40 votes

您可能感兴趣的是os.path.realpath,或者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版本应该更短。:)

Miles answered 2019-02-06T06:54:06Z
25 votes

我讨厌继续使用另一个实现,但我需要a)一个可移植的纯shell实现,以及b)单元测试覆盖,因为像这样的边缘情况的数量是非平凡的。

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

正如Keith Smith精明地指出的那样,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")
}

就是这样,或多或少。 很简单,可以粘贴到您的脚本中,但是非常棘手,您可能会依赖任何没有针对您的用例进行单元测试的代码。

Michael Kropat answered 2019-02-06T06:55:10Z
24 votes
  1. 安装自制软件
  2. 运行“brew install coreutils”
  3. 运行“greadlink -f path”

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

Andrew answered 2019-02-06T06:55:47Z
15 votes

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)"
JinnKo answered 2019-02-06T06:56:22Z
14 votes

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

#!/usr/bin/env python
import os.sys
print os.path.realpath(sys.argv[1])
James answered 2019-02-06T06:56:45Z
9 votes

那这个呢?

function readlink() {
  DIR=$(echo "${1%/*}")
  (cd "$DIR" && echo "$(pwd -P)")
}
Peeeech answered 2019-02-06T06:57:04Z
8 votes

FreeBSD和OSX的版本为perl,来自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版本的perl可能缺少格式的-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

将工作。

G. Cito answered 2019-02-06T06:57:54Z
6 votes

这是一个便携式shell函数,应该可以在任何Bourne类似的shell中使用。它将解决相对路径标点符号“..或”。 和解除引用的符号链接。

如果由于某种原因你没有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##*/}"
}

您可以在我的google网站上找到此shell代码的更新版本:[http://sites.google.com/site/jdisnard/realpath]

编辑:此代码根据2子句(freeBSD样式)许可条款获得许可。可以通过以上链接到我的网站找到许可证的副本。

masta answered 2019-02-06T06:58:47Z
5 votes

解决这个问题并在Mac上安装Homebrew或FreeBSD时启用readlink功能的最简单方法是安装'coreutils'软件包。 在某些Linux发行版和其他POSIX OS上也可能是必需的。

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

$ brew install coreutils

在使用Homebrew的MacOS上,命令将是:

$ brew install coreutils

不确定为什么其他答案如此复杂,这就是它的全部内容。 这些文件不在同一个地方,它们还没有安装。

AveryFreeman answered 2019-02-06T06:59:42Z
3 votes

开始更新

这是一个经常出现的问题,我们将一个名为realpath-lib的免费使用的Bash 4库(MIT许可证)组合在一起。 这是为了模拟readlink -f默认设置,包括两个测试套件,用于验证(1)它是否适用于给定的unix系统;(2)如果安装了readlink -f(但这不是必需的)。 此外,它还可用于调查,识别和展开深层,破损的符号链接和循环引用,因此它可以成为诊断深层嵌套的物理或符号目录和文件问题的有用工具。 它可以在github.com或bitbucket.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兼容的退出代码以进行测试。

AsymLabs answered 2019-02-06T07:00:39Z
3 votes

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

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"
}
estani answered 2019-02-06T07:01:01Z
1 votes

我想,迟到总比没有好。 我有动力专门开发这个,因为我的Fedora脚本不能在Mac上运行。 问题是依赖和Bash。 Mac没有它们,或者如果它们有,它们通常在其他地方(另一条路径)。 跨平台Bash脚本中的依赖路径操作最多是头痛,最坏的情况是安全风险 - 因此,如果可能的话,最好避免使用它们。

下面的函数get_realpath()很简单,以Bash为中心,不需要依赖。 我只使用Bash builtins echo和cd。 它也相当安全,因为一切都在路的每个阶段进行测试,并返回错误条件。

如果您不想遵循符号链接,那么将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+,但如果它们还在,可以使用旧版本。

AsymLabs answered 2019-02-06T07:01:56Z
1 votes

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

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

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

Holger Brandl answered 2019-02-06T07:02:37Z
0 votes

我为OS X编写了一个realpath实用程序,可以提供与sudo port selfupdate && sudo port install realpath相同的结果。


这是一个例子:

(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

user454322 answered 2019-02-06T07:03:25Z
-1 votes

这是我用的:

stat -f %N $your_path

Carlos Nunez answered 2019-02-06T07:03:51Z
-2 votes

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

/sw/sbin/readlink -f

ennuikiller answered 2019-02-06T07:04:22Z
-2 votes

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
jonjlee answered 2019-02-06T07:04:48Z
-2 votes

@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"
scavenger answered 2019-02-06T07:05:30Z
translate from https://stackoverflow.com:/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac