【问题标题】:How to list all members of a symlink-chain?如何列出符号链接链的所有成员?
【发布时间】:2016-01-20 05:20:54
【问题描述】:

查找 java 二进制文件可能很痛苦:

  • which java/usr/bin/java
  • lh $(which java)/usr/bin/java -> /etc/alternatives/java
  • lh /etc/alternatives/java/etc/alternatives/java -> /usr/lib/jvm/jdk1.8.0_66/bin/java

有没有办法自动跟踪符号链接并打印所有成员?例如whichfollowfollow /usr/bin/java 可以给:

/usr/bin/java
-> /etc/alternatives/java
-> /usr/lib/jvm/jdk1.8.0_66/bin/java

【问题讨论】:

  • @AnthonyGeoghegan:这不是重复的,因为这个问题是关于打印符号链接的
  • @mklement0 我后来想通了(然后提供了一个替代答案),但我认为不可能取消标记。无论如何,我留下了评论,因为相关问题的答案对于有兴趣了解符号链接如何解析的人仍然有用。很好的答案,你自己。

标签: bash symlink symlink-traversal


【解决方案1】:

除了readlink 命令,GNU/Linux 用户还可以使用util-linux 包中的namei 命令。根据其手册页:

namei 使用它的参数作为任何类型的 Unix 文件(符号链接、文件、目录等)的路径名。然后 namei 遵循每个路径名,直到找到端点(文件、目录、设备节点等)。如果它找到一个符号链接,它会显示该链接,并开始跟随它,缩进输出以显示上下文。

它的输出并不像你想要的那么漂亮,但它显示了每条路径 组件并显示它是目录、符号链接、套接字、块设备、字符设备、FIFO(命名管道)还是常规文件。

示例用法:

$ namei  /usr/bin/java

f: /usr/bin/java
 d /
 d usr
 d bin
 l java -> /etc/alternatives/java
   d /
   d etc
   d alternatives
   l java -> /usr/lib/jvm/jre-1.7.0-openjdk/bin/java
     d /
     d usr
     d lib
     d jvm
     l jre-1.7.0-openjdk -> java-1.7.0-openjdk-1.7.0.85/jre
       d java-1.7.0-openjdk-1.7.0.85
       d jre
     d bin
     - java

【讨论】:

  • 好东西;看起来namei 已预装在许多发行版上,并且可以列出其他信息,例如模式位。我希望它做的一件事是将相对目标路径解析为绝对路径;没有它,所引用的路径并不总是很明显,尤其是.. 组件。
【解决方案2】:

注意:namei(见Anthony Geoghegan's answer)和chase(见Toby Speight's answer)是很好的Linux选项;这个答案提供:
* 跨平台解决方案
* 为链的每一步打印绝对路径,即使符号链接是用相对路径定义的。

  • 考虑 typex utility(由我编写),它在每个步骤中使用 absolute 路径打印$PATH 中给定实用程序的符号链接链( typex 还提供了与type 类似但比type 更广泛的附加信息)。
    • 最简单的安装,安装了 Node.js:npm install typex -g
    • 示例(请注意如何附加使用--version 获得的版本信息 - 但不适用于使用-versionjava):
        $ typex awk
        BINARY:  /usr/bin/awk@ -> /etc/alternatives/awk@ -> /usr/bin/gawk  [GNU Awk 4.0.1]
  • rreadlink 是一个较低级别的实用程序(由我编写),它将符号链接链打印为任何给定文件系统路径的绝对路径。

    • 最简单的安装,安装了 Node.js:npm install rreadlink -g
    • 例子:

      $ rreadlink -1 "$(which awk)"
      /usr/bin/awk
      /etc/alternatives/awk
      /usr/bin/gawk
      
  • 下面是rreadlinkchain(),一个完全符合 POSIX 的脚本/函数 - 它仅使用 POSIX shell language features 和仅符合 POSIX 的实用程序调用。它是上述两个实用程序的核心bash 函数的一个符合 POSIX 标准的变体,并且非常感谢地改编自 this answer;适用于您的示例:rreadlinkchain "$(which java)"

兼容性说明:
typexrreadlink 从 npm 注册表安装时,支持 OS X 和 Linux,但它们手动安装时,可能也可以在带有 bash 的 BSD 系统上运行。
如前所述,下面的rreadlinkchain() 函数 完全符合 POSIX 标准,应该可以在大多数类 Unix 平台上运行。

#!/bin/sh

## -------
# SYNOPSIS
#   rreadlinkchain <symLink>
# DESCRIPTION
#  Recursive readlink: prints the CHAIN OF SYMLINKS from the input
#  file to its ultimate target, as ABSOLUTE paths, with each path on a separate
#  line.
#  Only the ultimate target's path is canonical, though.
#  A broken symlink in the chain causes an error that reports the
#  non-existent target.
#  An input path that is not a symlink will print its own canonical path.
# LIMITATIONS
#   - Won't work with filenames with embedded newlines or filenames containing 
#     the string ' -> '.
# COMPATIBILITY
#   Fully POSIX-compliant.
# EXAMPLES
#     # Print the symlink chain of the `git` executable in the $PATH.
#   rreadlinkchain  "$(which git)"
#    # Ditto, using single-line `ls -l`-style format ('a@ -> b')
#   rreadlinkchain  "$(which git)" | sed -nE -e '$!{a\'$'\n''@ -> ' -e '}; p' | tr -d '\n'
# THANKS
#   https://stackoverflow.com/a/1116890/45375
rreadlinkchain() ( # execute in *subshell* to localize the effect of `cd`, ...

  target=$1 targetDir= targetName= 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
      # Unless the file is a symlink OR exists, we report an error - note that using `-e` with a symlink reports the *target*'s existence, not the symlink's.
    [ -L "$target" ] || [ -e "$target" ] || { command printf '%s\n' "ERROR: '$target' does not exist." 1>&2; return 1; }
      # !! We use `cd` to change to the target's folder
      # !! so we can correctly resolve the full dir. path.
    command cd "$(command dirname -- "$target")" # note: cd "" is the same as cd . - i.e., a no-op.
    targetDir=$PWD
    targetName=$(command basename -- "$target")
    [ "$targetName" = '/' ] && targetName='' # !! curiously, `basename /` returns '/'
    done=0
    if [ ! -L "$targetName" ]; then
        # We've found the ultimate target (or the input file wasn't a symlink to begin with).
        # For the *ultimate* target we want use `pwd -P` to make sure we use the actual, physical directory,
        # (not a symlink) to get the *canonical* path.
      targetDir=$(command pwd -P)
      done=1
    fi
      # Print (next) path - note that we manually resolve paths ending 
      # in /. and /.. to make sure we have a normalized path.
    if [ "$targetName" = '.' ]; then
      command printf '%s\n' "${targetDir%/}"
    elif  [ "$targetName" = '..' ]; 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%/}/$targetName"
    fi
      # Exit, if we've hit the non-symlink at the end of the chain.
    [ "$done" = 1 ] && break 
    # File is symlink -> continue to resolve.
    # Parse `ls -l` output, which, unfortunately, is the only POSIX-compliant
    # way to determine a symlink's target. Hypothetically, this can break with
    # filenames containig literal ' -> ' and embedded newlines.
    target=$(command ls -l -- "$targetName")
    target=${target#* -> }
  done
)

rreadlinkchain "$@"

【讨论】:

    【解决方案3】:

    考虑安装chase:

    示例输出:

    $ chase --verbose /usr/bin/java
    /usr/bin/java
    -> /etc/alternatives/java
    -> /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java
    /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java
    

    包装说明:

    :chase
    状态:已安装
    自动安装:无
    版本:0.5.2-4build2
    优先级:可选
    部分:宇宙/实用程序
    维护者:Ubuntu开发人员架构:amd64
    未压缩大小:61.4 k
    取决于:libc6 (>= 2.3.4)、libgc1c2 ( >= 1:7.2d)
    冲突:追逐
    描述:跟随符号链接并打印出其目标文件
    Chase 是一个小型实用程序,用于跟踪 符号链接指向 - 追逐符号链接,如果你愿意的话。这 成功运行的结果保证是一个现有的文件 不是符号链接。

    【讨论】:

    • 很高兴知道;也很高兴看到chase 具有符号链接循环检测。与namei 不同,chase 值得称道的是总是将 ultimate 目标路径打印为 absolute 路径,但我希望它也能用于 intermediate目标被定义为 relative 路径的那些(可能在 addition 到相对路径中),因为并不总是很明显正在引用什么路径,尤其是..
    【解决方案4】:

    您可以将readlinkwhile 循环一起使用。下面的函数可以工作:

    function follow {
      path="$1"
      echo "$path"
      while path=$(readlink "$path"); do
        echo "-> $path"
      done
    }
    
    follow "/usr/bin/java"
    

    【讨论】:

    • 这不适用于相对链接,除非它们恰好位于当前工作目录中(或者,如果开头有一个或多个 .. 组件,有时是表亲目录。跨度>
    猜你喜欢
    • 1970-01-01
    • 2011-01-29
    • 1970-01-01
    • 2020-10-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-18
    • 1970-01-01
    相关资源
    最近更新 更多