【问题标题】:How to recursively resolve symlinks without readlink or realpath?如何在没有 readlink 或 realpath 的情况下递归解析符号链接?
【发布时间】:2015-10-14 06:57:39
【问题描述】:

如果readlinkrealpath 不可用,用脚本查找链接目标的最佳可移植(POSIX?)方法是什么?

您会ls -l,如果它以l 开头,则将-> 之后的文本与sed 一起重复,直到它不再以l 开头?

【问题讨论】:

    标签: shell posix sh


    【解决方案1】:

    根据BashFAQ #29(也支持 GNU 查找方法suggested by @EugeniuRosca):

    一个广泛可用的(虽然不是纯 POSIX)选项是使用perl

    target=/path/to/symlink-name perl -le 'print readlink $ENV{target}'
    

    如果你的符号链接的名字保证不包含->,你可以解析ls的输出。


    下面的代码结合了这两种方法:

    # define the best readlink function available for this platform
    if command -v readlink >/dev/null 2>/dev/null; then
      # first choice: Use the real readlink command
      readlink() {
        command readlink -- "$@"
      }
    elif find . -maxdepth 0 -printf '%l' >/dev/null 2>/dev/null; then
      # second choice: use GNU find
      readlink() {
        local ll candidate >/dev/null 2>&1 ||:
        if candidate=$(find "$1" -maxdepth 0 -printf '%l') && [ "$candidate" ]; then
          printf '%s\n' "$candidate"
        else
          printf '%s\n' "$1"
        fi
      }
    elif command -v perl >/dev/null 2>/dev/null; then
      # third choice: use perl
      readlink() {
        local candidate ||:
        candidate=$(target=$1 perl -le 'print readlink $ENV{target}')
        if [ "$candidate" ]; then
          printf '%s\n' "$candidate"
        else
          printf '%s\n' "$1"
        fi
      }
    else
      # fourth choice: parse ls -ld
      readlink() {
        local ll candidate >/dev/null 2>&1 ||:
        ll=$(LC_ALL=C ls -ld -- "$1" 2>/dev/null)
        candidate=${ll#* -> }
        if [ "$candidate" = "$ll" ]; then
          printf '%s\n' "$1"
        else
          printf '%s\n' "$candidate"
        fi
      }
    fi
    
    readlink_recursive() {
        local path prev_path oldwd found_recursion >/dev/null 2>&1 ||:
        oldwd=$PWD; path=$1; found_recursion=0
    
        while [ -L "$path" ] && [ "$found_recursion" = 0 ]; do
            if [ "$path" != "${path%/*}" ]; then
              cd -- "${path%/*}" || {
                cd -- "$oldwd" ||:
                echo "ERROR: Directory '${path%/*}' does not exist in '$PWD'" >&2
                return 1
              }
              path=${PWD}/${path##*/}
            fi
            path=$(readlink "$path")
            if [ -d "$path" ]; then
              cd -- "$path"
              path=$PWD
              break
            fi
            if [ "$path" != "${path%/*}" ]; then
              cd -- "${path%/*}" || {
                echo "ERROR: Could not traverse from $PWD to ${path%/*}" >&2
                return 1
              }
              path=${PWD}/${path##*/}
            elif [ "$PWD" != "$oldwd" ]; then
              path=${PWD}/$path
            fi
            for prev_path; do
              if [ "$path" = "$prev_path" ]; then
                found_recursion=1
                break
              fi
            done
            set -- "$path" "$@" # record path for recursion check
        done
    
        if [ "$path" != "${path%/../*}" ]; then
          cd "${path%/*}" || {
            echo "ERROR: Directory '${path%/*}' does not exist in $PWD" >&2
            return 1
          }
          printf '%s\n' "$PWD/${path##*/}"
        else
          printf '%s\n' "$path"
        fi
        cd -- "$oldwd" ||:
    }
    

    【讨论】:

    • 如果ls -- . 出现错误退出,则必须删除--。以破折号开头的文件名不起作用(如果前面没有目录名)。
    • 查了一下——实际上,在 POSIX 规范的当前版本中,支持-- 是强制性的。请参阅pubs.opengroup.org/onlinepubs/000095399/basedefs/…中的实用程序语法指南的指南 10@
    • 我已经对其进行了相当大的扩展,以正确处理更改目录的相对符号链接。不客气。 :)
    • ||: 告诉 shell 即使该行失败也将其视为不是错误(如果set -e 正在使用,则防止自动退出),从而让它声明事情local 如果它支持该功能,否则忽略该行。大多数/bin/sh 实现支持local,但POSIX 标准不要求它们。
    • 顺便说一句,||: 只是|| true 的常用习语; :true 的同义词(字面意思是,在大多数 shell 中调用完全相同的内置命令)。
    【解决方案2】:

    限制:-printf 不是 POSIX 指定的选项

    #!/bin/bash
    
    tmp=<symlink-name>
    tmp1=''
    while tmp=$(find "$tmp" -prune -printf "%l" 2>/dev/null); do
        target="$tmp1" && tmp1="$tmp"
    done;
    echo "$target"
    

    【讨论】:

    • @Charles Duffy:-maxdepth 替换为 -prune。我留下这个答案只是为了鼓舞人心的目的,在标题中有大胆的限制。
    • 这是一个很好的答案,对于可以使用 GNU 工具的系统——正如我在标题中指出的那样,它是 BashFAQ #29 认可的方法之一。
    【解决方案3】:

    这是另一个与 Charles Duffy 非常相似的解决方案。我没有那么有经验,所以可能存在一些非 POSIX 或性能问题。在查看了 Charles 的解决方案并替换了我不理解的任何内容后,我得出了这个结论:-P 很可能在解决了这里的任何问题之后,您最终会再次使用 Charles 的解决方案。

    resolve() {
        local arg path absolute ll dir prev_path oldwd found_recursion base >/dev/null 2>&1 ||:
        arg="$1"; path="$1"; oldwd=$PWD; found_recursion=0
        dir=$(dirname "$path")
        cd -- "$dir" || {
            cd -- "$oldwd" ||:
            echo "While resolving '$arg' could not go to '$dir'" >&2
            return 1
        }
        if [ $PWD = "/" ]; then
            absolute="/$(basename $path)"
        else
            absolute="$PWD/$(basename $path)"
        fi
        [ "$path" != "$absolute" ] && set -- "$absolute"
        while [ -L "$absolute" ] && [ "$found_recursion" = 0 ]; do
            ll=$(LC_ALL=C \ls -ld -- "$absolute" 2>/dev/null)
            path=${ll#* -> }
            dir=$(dirname "$path")
            cd -- "$dir" || {
                cd -- "$oldwd" ||:
                echo "While resolving '$arg' could not go to '$dir'" >&2
                return 1
            }
            base=$(basename "$path")
            absolute="$PWD/$base"
            for prev_path; do
                if [ "$absolute" = "$prev_path" ]; then
                    found_recursion=1
                    break
                fi
            done
            set -- "$absolute" "$@"
        done
        if [ -d "$absolute" ]; then
            cd -- "$absolute" || {
                cd -- "$oldwd" ||:
                echo "While resolving '$arg' could not go to '$absolute'" >&2
                return 1
            }
            printf '%s\n' "$PWD"
        else
            printf '%s\n' "$absolute"
        fi
    }
    

    编辑:现在使用$PWDprintf,如果是目录,则将结果规范化。

    【讨论】:

    • $(pwd) 的效率远低于$PWD,因为——就像任何$(...) 扩展一样——它分叉出一个子shell,在该子shell 中运行一个操作,读取它的标准输出,等待它退出,并替换该输出。
    • 另外,您在引用方面存在错误。当path 包含空格时,$(basename $path) 会严重中断。至少使用$(basename "$path"),尽管这仍然比等效的参数扩展效率低得多。
    • 另外,不平凡地使用echo 是不好的形式——echo 的作用因平台而异。参见pubs.opengroup.org/onlinepubs/009604599/utilities/echo.html:某些版本的echo(POSIX+XSI)默认展开反斜杠扩展;在某些情况下,反斜杠扩展的行为是未定义的;处理仅包含 "-n" 的字符串时的行为未定义;处理字符串-e 标准定义的(符合标准的实现必须在其输出上打印-e),但是包括bash 在内的一些实现在这方面不符合标准。
    • 否则,这并不可怕。因为它总是使用ls -ld,而不是在可用时首选 GNU find 或 perl,所以它无法正确处理名称中包含 ` -> ` 的符号链接,并且链接目标中不可打印字符的行为可能会被破坏,但如果你只是试图处理行为良好的文件名(你很幸运!),这可能是可以接受的。
    • 说到性能——我已经更新了我的版本,以便在定义函数之前只运行一次测试以确定最佳可用的 readlink 实现,而不是每次调用 readlink 时都这样做。跨度>
    猜你喜欢
    • 2011-06-14
    • 2011-11-17
    • 1970-01-01
    • 1970-01-01
    • 2013-03-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多