如何在Mac上获得GNU的readlink -f的行为?
在Linux上, readlink
实用程序接受附加链接readlink
的选项-f
。 这似乎不适用于Mac和BSD系统。 相当于什么?
这里有一些debugging信息:
$ which readlink; readlink -f /usr/bin/readlink readlink: illegal option -f usage: readlink [-n] [file ...]
readlink -f
做两件事:
- 它沿着一系列符号链接进行迭代,直到find一个实际的文件。
- 它返回该文件的规范化名称,即其绝对path名。
如果你想,你可以build立一个使用vanilla readlink行为的shell脚本来实现同样的function。 这是一个例子。 显然,你可以在你想要调用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
请注意,这不包括任何error handling。 特别重要的是,它不检测符号链接周期。 一个简单的方法是计算循环次数,如果遇到不可能的大数目(如1,000),则失败。
编辑使用pwd -P
而不是$PWD
。
请注意,如果您希望能够使用-f filename
如GNU readlink),则此脚本预期将被称为./script_name filename
,否-f
,将$1
更改$1
$2
。
macports和homebrew提供了一个包含greadlink
的coreutils包(GNU readlink)。 信贷给迈克尔Kallweitt在mackb.com发布
brew install coreutils greadlink -f file.txt
您可能对realpath(3)
或Python的os.path.realpath
感兴趣。 二者不完全一样; C库调用需要存在中间path组件,而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版本应该更短。 🙂
我讨厌用另外一个实现来实现,但是我需要a) 一个可移植的,纯粹的shell实现 ,以及b) unit testing覆盖 ,因为像这样的边缘情况的数量并不是微不足道的 。
在Github上查看我的项目进行testing和完整的代码。 接下来是实现的概要:
正如Keith Smith精明地指出的那样, readlink -f
做了两件事:1)recursion地parsing符号链接,2)规范化结果,因此:
realpath() { canonicalize_path "$(resolve_symlinks "$1")" }
首先,符号链接parsing器的实现:
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 }
请注意,这是完整实现的稍微简化的版本。 完整的实现为符号链接周期添加了一个小的检查 ,并且对输出进行了一些处理。
最后,规范一个path的函数:
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") }
就是这样,或多或less。 足够简单的粘贴到你的脚本中,但是很棘手,你会疯狂地依赖任何没有为你的用例进行unit testing的代码。
- 安装自制软件
- 运行“brew install coreutils”
- 运行“greadlink -fpath”
greadlink是实现-f的gnu readlink。 你也可以使用macports或其他的,我更喜欢自制软件。
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)"
我个人做了一个名为realpath的脚本,看起来有点像:
#!/usr/bin/env python import os,sys print os.path.realpath(sys.argv[1])
那这个呢?
function readlink() { DIR=$(echo "${1%/*}") (cd "$DIR" && echo "$(pwd -P)") }
FreeBSD和OSX有一个从NetBSD派生的stat
版本。
您可以使用格式开关调整输出(请参阅上述链接的手册页)。
% 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
可能缺less格式的-f%R
选项。 在这种情况下-stat -f%Y
就足够了。 -f%Y
选项将显示符号链接的目标,而-f%R
显示与该文件相对应的绝对path名。
编辑:
如果你能够使用Perl(Darwin / OS X附带最近版本的perl
),那么:
perl -MCwd=abs_path -le 'print abs_path readlink(shift);' linkedfile.txt
将工作。
这是一个可移植的shell函数,应该可以在任何 Bourne类似的shell中工作。 它将parsing相对path标点“..或”。 并取消引用符号链接。
如果由于某种原因,您没有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站点find这个shell代码的更新版本: http : //sites.google.com/site/jdisnard/realpath
编辑:此代码是根据2条款(FreeBSD风格)许可条款许可。 上面的超链接到我的网站可以find许可证的副本。
解决这个问题最简单的方法是启用Mac上的readlinkfunction,或者安装FreeBSD,安装'coreutils'软件包。 在某些Linux发行版和其他POSIX操作系统上也可能是必需的。
例如,在FreeBSD 11中,我通过调用:
# pkg install coreutils
在MacOS上用Homebrew,命令是:
$ brew install coreutils
不太确定为什么其他答案如此复杂,这就是它的全部。 文件不在不同的地方,他们只是没有安装。
开始更新
这是一个很常见的问题,我们把一个免费使用的Bash 4库(MIT License)放在一起,叫做realpath-lib 。 这是为了模拟readlink -f默认情况下,包括两个testing套件来validation(1)它适用于给定的unix系统和(2)对readlink -f如果安装(但这不是必需的)。 此外,它可以用来调查,识别和解开深层的,破坏的符号链接和循环引用,因此它可以成为诊断深层嵌套的物理或符号目录和文件问题的有用工具。 它可以在github.com或bitbucket.orgfind。
结束更新
另一个非常紧凑和有效的解决scheme,不依赖于任何东西,但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
,它提供了parsing符号链接到物理系统的能力。 只要no_symlinks
设置为no_symlinks='on'
那么符号链接将被parsing为物理系统。 否则,它们将被应用(默认设置)。
这应该在任何提供Bash的系统上工作,并且将返回一个Bash兼容的退出代码用于testing目的。
已经有很多答案,但没有一个为我工作…所以这就是我现在使用的。
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" }
我猜想,比迟到好得多。 我很想专门开发这个function,因为我的Fedora脚本在Mac上不能正常工作。 问题是依赖和Bash。 Macs没有他们,或者如果他们这样做,他们往往是别的地方(另一条path)。 跨平台的Bash脚本中的依赖path操作是最令人头痛的事情,最糟糕的是安全风险 – 所以最好尽可能避免使用它们。
下面的函数get_realpath()很简单,以Bash为中心,并且不需要依赖关系。 我只使用Bash builtins 回声和CD 。 这也是相当安全的,因为所有的事情都在testing的每个阶段进行,并且返回错误状态。
如果您不想遵循符号链接,则将set -P放在脚本的前面,否则cd应默认parsing符号链接。 已经使用{absolute |的文件参数进行了testing 相对| 符号链接| 本地},它返回文件的绝对path。 到目前为止,我们没有任何问题。
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仓库中findrealpath-lib (完全公开 – 这是我们的产品,但我们免费提供给社区没有任何限制)。 它也可以作为一个教学工具 – 这是有据可查的。
我们已经尽力应用所谓的“现代Bash”的做法,但Bash是一个很大的主题,我相信总会有改进的空间。 它需要Bash 4+,但如果他们仍然在附近,可以使用旧版本。
真正的平台独立也将是这个R-onliner
readlink(){ RScript -e "cat(normalizePath(commandArgs(T)[1]))" "$1";}
要真正模仿readlink -f <path>
,需要使用$ 2而不是$ 1。
我为OS X写了一个realpath实用程序,它可以提供与readlink -f
相同的结果。
这里是一个例子:
(jalcazar@mac tmp)$ ls -la 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
。
这是我使用的:
stat -f %N $your_path
readlink的path在我的系统和你的系统之间是不同的。 请尝试指定完整path:
/sw/sbin/readlink -f
Perl有一个readlink函数(例如, 如何复制Perl中的符号链接? )。 这适用于大多数平台,包括OS X:
perl -e "print readlink '/path/to/link'"
例如:
$ mkdir -pa/b/c $ ln -sa/b/cx $ perl -e "print readlink 'x'" a/b/c
凯史密斯的回答给出了一个无限循环。
这是我的答案,我只使用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"