如何在没有readlink或realpath的情况下递归解析符号链接?

时间:2015-07-23 19:24:37

标签: shell posix sh

如果readlinkrealpath不可用,那么为脚本查找链接目标的最佳便携式(POSIX?)方法是什么?

您是ls -l,如果它是以l开头,请->之后的文字sed并重复,直到它不再以l开头?

3 个答案:

答案 0 :(得分:5)

Per 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" ||:
}

答案 1 :(得分:1)

限制:-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"

答案 2 :(得分:0)

这是另一种与Charles Duffy非常相似的解决方案。我不是那么经验,所以可能会有一些非POSIX或性能问题。我看到Charles的解决方案并更换了任何我不理解的东西后到达了这里:-P很可能在这里的任何问题得到解决之后,你最终会再次找到Charles的解决方案。

npm info cordova

修改:现在使用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 } $PWD,在目录中规范化结果。