注意:我相信这是一个可靠的、便携的、现成的解决方案,正因为如此,它总是很冗长。
下面是一个完全兼容 POSIX 的脚本/函数,因此它是跨平台的(也适用于 macOS,但从 10.12 (Sierra) 开始readlink
仍不支持-f
)-它仅使用POSIX shell 语言功能和仅与 POSIX 兼容的实用程序调用.
它是GNU (的更严格版本)的可移植实现readlink -e
readlink -f
。
您可以使用、和中的函数运行脚本sh
或获取函数:bash
ksh
zsh
例如,在脚本中,您可以按如下方式使用它来获取运行脚本的真实原始目录,并解析符号链接:
trueScriptDir=$(dirname -- "$(rreadlink "$0")")
rreadlink
脚本/函数定义:
代码改编自这个答案的感激之情。
我还在这里bash
创建了一个基于 - 的独立实用程序版本,如果您安装了 Node.js,您可以使用它进行安装。
npm install rreadlink -g
#!/bin/sh
# SYNOPSIS
# rreadlink <fileOrDirPath>
# DESCRIPTION
# Resolves <fileOrDirPath> to its ultimate target, if it is a symlink, and
# prints its canonical path. If it is not a symlink, its own canonical path
# is printed.
# A broken symlink causes an error that reports the non-existent target.
# LIMITATIONS
# - Won't work with filenames with embedded newlines or filenames containing
# the string ' -> '.
# COMPATIBILITY
# This is a fully POSIX-compliant implementation of what GNU readlink's
# -e option does.
# EXAMPLE
# In a shell script, use the following to get that script's true directory of origin:
# trueScriptDir=$(dirname -- "$(rreadlink "$0")")
rreadlink() ( # Execute the function in a *subshell* to localize variables and the effect of `cd`.
target=$1 fname= targetDir= CDPATH=
# Try to make the execution environment as predictable as possible:
# All commands below are invoked via `command`, so we must make sure that
# `command` itself is not redefined as an alias or shell function.
# (Note that command is too inconsistent across shells, so we don't use it.)
# `command` is a *builtin* in bash, dash, ksh, zsh, and some platforms do not
# even have an external utility version of it (e.g, Ubuntu).
# `command` bypasses aliases and shell functions and also finds builtins
# in bash, dash, and ksh. In zsh, option POSIX_BUILTINS must be turned on for
# that to happen.
{ \unalias command; \unset -f command; } >/dev/null 2>&1
[ -n "$ZSH_VERSION" ] && options[POSIX_BUILTINS]=on # make zsh find *builtins* with `command` too.
while :; do # Resolve potential symlinks until the ultimate target is found.
[ -L "$target" ] || [ -e "$target" ] || { command printf '%s\n' "ERROR: '$target' does not exist." >&2; return 1; }
command cd "$(command dirname -- "$target")" # Change to target dir; necessary for correct resolution of target path.
fname=$(command basename -- "$target") # Extract filename.
[ "$fname" = '/' ] && fname='' # !! curiously, `basename /` returns '/'
if [ -L "$fname" ]; then
# Extract [next] target path, which may be defined
# *relative* to the symlink's own directory.
# Note: We parse `ls -l` output to find the symlink target
# which is the only POSIX-compliant, albeit somewhat fragile, way.
target=$(command ls -l "$fname")
target=${target#* -> }
continue # Resolve [next] symlink target.
fi
break # Ultimate target reached.
done
targetDir=$(command pwd -P) # Get canonical dir. path
# Output the ultimate target's canonical path.
# Note that we manually resolve paths ending in /. and /.. to make sure we have a normalized path.
if [ "$fname" = '.' ]; then
command printf '%s\n' "${targetDir%/}"
elif [ "$fname" = '..' ]; then
# Caveat: something like /var/.. will resolve to /private (assuming /var@ -> /private/var), i.e. the '..' is applied
# AFTER canonicalization.
command printf '%s\n' "$(command dirname -- "${targetDir}")"
else
command printf '%s\n' "${targetDir%/}/$fname"
fi
)
rreadlink "$@"
安全性的切线:
jarno参考确保 builtincommand
不会被同名的别名或 shell 函数遮蔽的函数,在评论中询问:
如果unalias
orunset
和[
被设置为别名或 shell 函数怎么办?
rreadlink
确保其具有其原始含义的动机command
是使用它绕过(良性)便利别名和函数,这些别名和函数通常用于隐藏交互式 shell 中的标准命令,例如重新定义ls
以包含最喜欢的选项。
我认为可以肯定地说,除非您处理的是不受信任的恶意环境,否则不必担心unalias
或unset
- 或者,就此而言,while
, do
, ... - 被重新定义不是问题。
函数必须依赖某些东西才能具有其原始含义和行为 - 没有办法解决这个问题。
类似 POSIX 的 shell 允许重新定义内置函数甚至语言关键字,这本质上是一种安全风险(而且编写偏执的代码通常很困难)。
具体解决您的疑虑:
该功能依赖于unalias
并unset
具有其本来的含义。以改变其行为的方式将它们重新定义为shell 函数将是一个问题。重新定义为别名不一定是一个问题,因为引用(部分)命令名称(例如,\unalias
)会绕过别名。
但是,引用不是shell关键字( while
, for
, if
, do
, ...) 的选项,虽然 shell 关键字确实优先于 shell函数,但 inbash
和zsh
别名具有最高优先级,因此要防止 shell 关键字重新定义,您必须unalias
运行它们的名称(尽管在非交互式 bash
shell(例如脚本)中,别名默认情况下不会扩展 - 仅当shopt -s expand_aliases
首先显式调用时)。
为了确保unalias
- 作为内置 - 具有其原始含义,您必须首先使用\unset
它,这需要unset
具有其原始含义:
unset
是一个 shell builtin,所以为了确保它被这样调用,你必须确保它本身没有被重新定义为一个function。虽然您可以通过引用绕过别名表单,但不能绕过 shell 函数表单 - catch 22。
因此,除非您可以依赖unset
它的原始含义,否则据我所知,没有保证可以防御所有恶意重新定义。