在Linux上,readlink实用程序接受附加链接后面的选项-f。这似乎不适用于Mac和基于BSD的系统。等价的是什么?
下面是一些调试信息:
$ which readlink; readlink -f
/usr/bin/readlink
readlink: illegal option -f
usage: readlink [-n] [file ...]
在Linux上,readlink实用程序接受附加链接后面的选项-f。这似乎不适用于Mac和基于BSD的系统。等价的是什么?
下面是一些调试信息:
$ which readlink; readlink -f
/usr/bin/readlink
readlink: illegal option -f
usage: readlink [-n] [file ...]
当前回答
一个简单的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)"
其他回答
您可能既需要可移植的纯shell实现,也需要单元测试覆盖率,因为此类情况的边缘情况的数量非常多。
查看我的项目在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")
}
差不多就是这样。简单到可以粘贴到您的脚本中,但也非常棘手,如果您依赖于任何用例中没有单元测试的代码,那就太疯狂了。
POSIX兼容的readlink -f实现POSIX shell脚本
https://github.com/ko1nksm/readlinkf
这是POSIX兼容的(没有bashism)。它既不使用readlink也不使用realpath。我已经通过与GNU readlink -f进行比较验证了它是完全相同的(参见测试结果)。它具有错误处理和良好的性能。可以安全地从readlink -f中替换。许可证是CC0,因此您可以将其用于任何项目。
此代码用于bat -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
}
请参考最新的代码。它可能有些固定。
一种适合我的偷懒方式,
$ brew install coreutils
$ ln -s /usr/local/bin/greadlink /usr/local/bin/readlink
$ which readlink
/usr/local/bin/readlink
/usr/bin/readlink
Readlink -f做两件事:
它沿着符号链接序列迭代,直到找到一个实际的文件。 它返回该文件的规范化名称。,它的绝对路径名。
如果您愿意,您可以构建一个shell脚本,使用普通readlink行为来实现相同的功能。举个例子。显然,你可以在自己的脚本中插入这个,你想调用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),就会失败。
edit使用pwd -P而不是$ pwd。
注意,这个脚本希望被称为。/script_name文件名,不使用-f,如果你想能够像GNU readlink一样使用-f文件名,将$1更改为$2。
我为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。