注意:我认为这是一个可靠,可移植的现成解决方案,因此这总是很长。
以下是完全兼容POSIX的脚本/函数,因此是跨平台的(也可在macOS上运行,从10.12(Sierra)开始readlink仍不支持-f)-它仅使用POSIX Shell语言功能,并且仅使用POSIX兼容的实用程序调用。
它是GNUreadlink -e(的更严格版本readlink -f)的可移植实现。
您可以运行该脚本用sh或源功能中bash,ksh和zsh:
例如,在脚本内部,可以按以下方式使用它来获取运行的脚本的真实原始目录,并解析符号链接:
trueScriptDir=$(dirname -- "$(rreadlink "$0")")
rreadlink 脚本/函数定义:
对此答案表示感谢。
我还创建了一个bash基于独立的实用程序的版本在这里,您可以与安装
npm install rreadlink -g,如果安装了Node.js的。
#!/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关于确保内置函数command不会被同名的别名或shell函数遮盖的函数,在注释中要求:
  如果将unaliasor或unsetand [设置为别名或shell函数怎么办?
rreadlink确保command具有其原始含义的动机是使用它绕过(良性)便捷别名和函数,这些别名和函数通常用于遮盖交互式外壳程序中的标准命令,例如重新定义ls以包括喜欢的选项。
我认为这是肯定地说,除非你正在处理一个不受信任的,恶意的环境,担心unalias或unset-或者,就此而言,while,do,... -被重新定义是不是一个问题。
有 函数必须某种东西才能具有其原始含义和行为-无法解决。
类似于POSIX的shell允许重新定义内建函数,甚至语言关键字也固有地存在安全风险(并且编写偏执代码通常很困难)。
具体解决您的问题:
该功能依赖unalias并unset具有其原始含义。以改变行为的方式将它们重新定义为shell函数将是一个问题;重新定义作为别名不一定是一个问题,因为引用命令名(例如\unalias)(的一部分)会绕开别名。
然而,引用是不是壳的选项关键字(while,for,if,do,...),并同时外壳的关键字做优先于外壳的功能,在bash和zsh别名具有最高的优先级,所以对你必须运行壳关键字重定义后卫unalias与它们的名称(尽管在非交互式 bash外壳(例如脚本)中)是别名默认情况下不扩展-仅在shopt -s expand_aliases首先显式调用时才使用)。
为了确保unalias-作为内置函数-具有其原始含义,您必须使用\unset首先在其上,这需要unset具有其原始含义:
unset是内置的shell,因此要确保以这种方式调用它,必须确保它本身没有被重新定义为function。虽然可以用引号绕过别名表单,但不能绕过Shell函数形式-catch 22。
因此,据unset我所知,除非您可以依靠它具有其原始含义,否则无法保证防御所有恶意重新定义的方法。