How can I get the behavior of GNU's readlink -f on a Mac?
在Linux上,
以下是一些调试信息:
1 2 3 4 | $ which readlink; readlink -f /usr/bin/readlink readlink: illegal option -f usage: readlink [-n] [file ...] |
MacPorts和自制提供了包含
1 2 3 | brew install coreutils greadlink -f file.txt |
如果您愿意,您可以构建一个shell脚本,它使用普通的readlink行为来实现相同的功能。下面是一个例子。很明显,您可以在自己的脚本中插入这个命令,您可以在其中调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #!/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,就会失败。
编辑为使用
请注意,如果您希望能够像gnu readlink那样使用
您可能对
1 2 3 4 5 6 7 8 9 | $ 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上可用)来包装库调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #!/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)单元测试覆盖率,因为这样的边缘案例数量是不平凡的。
有关测试和完整代码,请参阅我在GitHub上的项目。以下是实施概要:
正如基思·史密斯敏锐地指出的那样,
1 2 3 | realpath() { canonicalize_path"$(resolve_symlinks"$1")" } |
首先,symlink解析器实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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 '"$1" fi } _prepend_path_if_relative() { case"$2" in /* ) printf '%s '"$2" ;; * ) printf '%s '"$1/$2" ;; esac } |
注意,这是一个稍微简化的完整实现版本。完整的实现增加了对symlink循环的小检查,并稍微按摩输出。
最后,用于规范化路径的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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 '"$(pwd -P)""$file") } |
差不多就是这样。简单到可以粘贴到您的脚本中,但是复杂到可以让您疯狂地依赖任何没有单元测试的代码来处理您的用例。
greadlink是实现-f的GNU readlink。您也可以使用MacPorts或其他,我更喜欢自制。
我制作了一个名为realpath的脚本,看起来有点像:
1 2 3 | #!/usr/bin/env python import os.sys print os.path.realpath(sys.argv[1]) |
Perl中的一个简单的一行程序,在没有任何外部依赖项的情况下几乎可以在任何地方工作:
1 | perl -MCwd -e 'print Cwd::abs_path shift' ~/non-absolute/file |
将取消引用符号链接。
脚本中的用法如下:
1 2 | readlinkf(){ perl -MCwd -e 'print Cwd::abs_path shift'"$1";} ABSPATH="$(readlinkf ./non-absolute/file)" |
这个怎么样?
1 2 3 4 | function readlink() { DIR="${1%/*}" (cd"$DIR" && echo"$(pwd -P)") } |
freebsd和osx有一个源自netbsd的
您可以使用格式开关调整输出(请参阅上面链接处的手册页面)。
1 2 3 4 5 6 7 8 9 10 11 | % 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 |
编辑:
如果您能够使用Perl(Darwin/OSX安装了最新版本的
1 | perl -MCwd=abs_path -le 'print abs_path readlink(shift);' linkedfile.txt |
会工作。
这里有一个可移植的shell函数,它可以在任何Bourne类似的shell中工作。它将解析相对路径标点"…或者"取消引用符号链接。
如果出于某种原因,您没有realpath(1)命令或readlink(1),则可以使用别名。
1 | which realpath || alias realpath='real_path' |
享受:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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:
1 2 3 4 5 6 7 8 9 10 11 | ## 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样式)许可证的条款授权的。通过访问上述"我的网站"超链接,可以找到许可证的副本。
要解决此问题并启用Mac w/homebrew installed或freebsd上的readlink功能,最简单的方法是安装"coreutils"包。在某些Linux发行版和其他POSIX操作系统上也可能是必需的。
例如,在FreeBsd 11中,我通过调用以下命令来安装:
在带有自制的MacOS上,命令为:
不太清楚为什么其他的答案如此复杂,这就是全部。这些文件不在另一个地方,只是还没有安装。
开始更新
这是一个非常常见的问题,我们已经构建了一个免费使用的bash 4库(MIT许可证),称为realpath lib。这是为了在默认情况下模拟readlink-f而设计的,包括两个测试套件来验证(1)它对给定的UNIX系统有效,(2)如果安装了readlink-f(但这不是必需的)。此外,它还可以用于调查、识别和展开深度、中断的符号链接和循环引用,因此它可以成为诊断深度嵌套的物理或符号目录和文件问题的有用工具。它可以在github.com或bitback.org上找到。
结束更新
另一个非常紧凑和高效的解决方案是:
1 2 3 4 5 6 7 8 | 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 } |
这还包括设置
这应该适用于任何提供bash的系统,并将返回与bash兼容的退出代码以进行测试。
已经有很多答案了,但没有一个对我有用…这就是我现在使用的。
1 2 3 4 5 6 7 8 9 | 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" } |
由于我的工作被非BSD Linux和MacOS用户使用,我选择在构建脚本中使用这些别名(包括
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | ## # 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依赖项:
1 2 3 4 5 6 7 8 9 10 | 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分。
真正的平台独立也将是这个R-onliner
1 | readlink(){ RScript -e"cat(normalizePath(commandArgs(T)[1]))""$1";} |
要真正模仿
我想迟到总比不迟到好。我有动力专门开发这个,因为我的Fedora脚本不在Mac上工作。问题是依赖性和bash。Mac没有,或者如果有,它们通常在其他地方(另一条路径)。跨平台bash脚本中的依赖路径操作充其量是一件令人头疼的事情,最坏的情况下是一个安全风险——因此,如果可能的话,最好避免使用它们。
下面的get realpath()函数很简单,以bash为中心,不需要依赖项。我只使用bash内置的echo和cd。它也是相当安全的,因为在方法的每个阶段都要测试所有内容,并且它返回错误条件。
如果您不想遵循symlinks,那么将set-p放在脚本的前面,否则cd默认应该解析symlinks。它通过文件参数绝对相对符号链接本地进行测试,并返回文件的绝对路径。到目前为止,我们还没有遇到任何问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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 u dirname、get u filename、get u stemname和validate u path。这些可以在我们的Github存储库中找到,作为realpath lib(完全公开-这是我们的产品,但我们免费提供给社区,没有任何限制)。它还可以作为一种教学工具——它有很好的文档记录。
我们已经尽力应用所谓的"现代bash"实践,但是bash是一个大主题,我相信总会有改进的空间。它需要bash 4+,但是如果旧版本仍然存在的话,可以使用它们。
我为OSX编写了一个realpath实用程序,它可以提供与
下面是一个例子:
1 2 3 4 5 | (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,则可以使用以下命令进行安装:
这是我使用的:
我的系统和您的系统之间的readlink路径是不同的。请尝试指定完整路径:
/sw/sbin/readlink -f
Perl有一个readlink函数(例如,如何在Perl中复制符号链接?)。这适用于大多数平台,包括OS X:
1 | perl -e"print readlink '/path/to/link'" |
例如:
1 2 3 4 | $ mkdir -p a/b/c $ ln -s a/b/c x $ perl -e"print readlink 'x'" a/b/c |
@keith smith给出了一个无限循环。
这是我的答案,我只在sunos上使用(sunos会错过很多posix和gnu命令)。
这是一个脚本文件,您必须将其放入$path目录之一:
1 2 3 4 5 6 7 8 9 10 11 12 13 | #!/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" |