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

问题描述 投票:321回答:22

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

这是一些调试信息:

$ which readlink; readlink -f
/usr/bin/readlink
readlink: illegal option -f
usage: readlink [-n] [file ...]
macos freebsd sh
22个回答
162
投票

readlink -f做了两件事:

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

如果您愿意,您可以构建一个使用vanilla readlink行为的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

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

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

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


7
投票

这是一个便携式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样式)许可条款获得许可。可以通过以上链接到我的网站找到许可证的副本。


6
投票

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

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

# pkg install coreutils

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

$ brew install coreutils

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


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兼容的退出代码以进行测试。


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"
}

1
投票

我想,迟到总比没有好。我有动力专门开发这个,因为我的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+,但如果它们还在,可以使用旧版本。


1
投票

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

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

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


1
投票

由于我的工作是由非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标记。


0
投票

我写了a realpath utility for OS X,可以提供与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


0
投票

说明

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

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

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

这将安装相应的核心实用程序,前缀为g。例如。对于readlink,你会发现相应的greadlink计划。

为了使readlink像GNU readlinkgreadlink)实现一样,您可以在安装coreutils后创建一个简单的别名。

履行

  1. 安装brew

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

  1. 安装coreutils包

brew install coreutils

  1. 创建别名

您可以将别名放在〜/ .bashrc,〜/ .bash_profile中,或者用于保留bash别名的任何位置。我亲自把我的东西放在〜/ .bashrc中

alias readlink=greadlink

您可以为其他coreutils创建类似的别名,例如gmv,gdu,gdf等。但请注意,mac机器上的GNU行为可能会使用于处理本机coreutils的其他人感到困惑,或者可能会在您的mac系统上以意想不到的方式运行。


-1
投票

这是我使用的:

stat -f %N $your_path


396
投票

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

brew install coreutils

greadlink -f file.txt

-2
投票

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

/sw/sbin/readlink -f


-2
投票

Perl具有读取链接功能(例如How do I copy symbolic links in 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

-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"

42
投票

您可能对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版本应该更短。 :)


25
投票

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

有关测试和完整代码,请参阅my project on 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 
}

请注意,这是the full implementation的略微简化版本。完整的实现为符号链接周期添加了一个小的检查,并稍微按摩了输出。

最后,规范化路径的功能:

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")
}

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


24
投票
  1. 安装自制软件
  2. 运行“brew install coreutils”
  3. 运行“greadlink -f path”

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


21
投票

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)"

18
投票

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

#!/usr/bin/env python
import os.sys
print os.path.realpath(sys.argv[1])

11
投票

那这个呢?

function readlink() {
  DIR="${1%/*}"
  (cd "$DIR" && echo "$(pwd -P)")
}

8
投票

FreeBSDOSX有一个来自NetBSD的statderived版本。

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

%  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

将工作。

© www.soinside.com 2019 - 2024. All rights reserved.