如何在没有 readlink 或 realpath 的情况下递归解析符号链接?
How to recursively resolve symlinks without readlink or realpath?
如果 readlink
和 realpath
不可用,最好的可移植(POSIX?)编写脚本查找 link 目标的方法是什么?
你会 ls -l
,如果它以 l
开头,则将 ->
之后的文本替换为 sed
并重复直到它不再以 l
开头=] ?
限制:-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"
每 BashFAQ #29 (which also endorses the GNU find approach ):
一个广泛使用的(虽然不是纯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 "" -maxdepth 0 -printf '%l') && [ "$candidate" ]; then
printf '%s\n' "$candidate"
else
printf '%s\n' ""
fi
}
elif command -v perl >/dev/null 2>/dev/null; then
# third choice: use perl
readlink() {
local candidate ||:
candidate=$(target= perl -le 'print readlink $ENV{target}')
if [ "$candidate" ]; then
printf '%s\n' "$candidate"
else
printf '%s\n' ""
fi
}
else
# fourth choice: parse ls -ld
readlink() {
local ll candidate >/dev/null 2>&1 ||:
ll=$(LC_ALL=C ls -ld -- "" 2>/dev/null)
candidate=${ll#* -> }
if [ "$candidate" = "$ll" ]; then
printf '%s\n' ""
else
printf '%s\n' "$candidate"
fi
}
fi
readlink_recursive() {
local path prev_path oldwd found_recursion >/dev/null 2>&1 ||:
oldwd=$PWD; path=; 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" ||:
}
这是另一个与 Charles Duffy 的解决方案非常相似的解决方案。我经验不足,因此可能存在一些非 POSIX 或性能问题。在查看了 Charles 的解决方案并替换了我不明白的任何内容后,我得出了这个结论:-P 很可能在解决这里的任何问题后,您最终会再次使用 Charles 的解决方案。
resolve() {
local arg path absolute ll dir prev_path oldwd found_recursion base >/dev/null 2>&1 ||:
arg=""; path=""; 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
和 printf
,规范目录结果。
如果 readlink
和 realpath
不可用,最好的可移植(POSIX?)编写脚本查找 link 目标的方法是什么?
你会 ls -l
,如果它以 l
开头,则将 ->
之后的文本替换为 sed
并重复直到它不再以 l
开头=] ?
限制:-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"
每 BashFAQ #29 (which also endorses the GNU find approach
一个广泛使用的(虽然不是纯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 "" -maxdepth 0 -printf '%l') && [ "$candidate" ]; then
printf '%s\n' "$candidate"
else
printf '%s\n' ""
fi
}
elif command -v perl >/dev/null 2>/dev/null; then
# third choice: use perl
readlink() {
local candidate ||:
candidate=$(target= perl -le 'print readlink $ENV{target}')
if [ "$candidate" ]; then
printf '%s\n' "$candidate"
else
printf '%s\n' ""
fi
}
else
# fourth choice: parse ls -ld
readlink() {
local ll candidate >/dev/null 2>&1 ||:
ll=$(LC_ALL=C ls -ld -- "" 2>/dev/null)
candidate=${ll#* -> }
if [ "$candidate" = "$ll" ]; then
printf '%s\n' ""
else
printf '%s\n' "$candidate"
fi
}
fi
readlink_recursive() {
local path prev_path oldwd found_recursion >/dev/null 2>&1 ||:
oldwd=$PWD; path=; 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" ||:
}
这是另一个与 Charles Duffy 的解决方案非常相似的解决方案。我经验不足,因此可能存在一些非 POSIX 或性能问题。在查看了 Charles 的解决方案并替换了我不明白的任何内容后,我得出了这个结论:-P 很可能在解决这里的任何问题后,您最终会再次使用 Charles 的解决方案。
resolve() {
local arg path absolute ll dir prev_path oldwd found_recursion base >/dev/null 2>&1 ||:
arg=""; path=""; 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
和 printf
,规范目录结果。