Shell 如何在没有readlink或realpath的情况下递归解析符号链接?
如果Shell 如何在没有readlink或realpath的情况下递归解析符号链接?,shell,posix,sh,Shell,Posix,Sh,如果readlink和realpath不可用,那么脚本查找链接目标的最佳可移植(POSIX?)方法是什么 您是否愿意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"
readlink
和realpath
不可用,那么脚本查找链接目标的最佳可移植(POSIX?)方法是什么
您是否愿意
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"
#/bin/bash
tmp=
tmp1=''
而tmp=$(查找“$tmp”-prune-printf“%l”2>/dev/null);做
target=“$tmp1”和&tmp1=“$tmp”
完成;
回显“$target”
Per(也支持GNU查找方法):
一个广泛使用的选项(虽然不是纯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" ||:
}
这是另一个与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
}
编辑:现在使用
$PWD
和printf
,规范化结果if目录。@Charles Duffy:-maxdepth
替换为-prune
。我留下这个答案只是为了鼓舞人心,标题中有大胆的限制。对于GNU工具可用的系统来说,这是一个很好的答案——正如我在标题中指出的,这是BashFAQ认可的方法之一。如果--
出现错误,则必须删除--
。退出。以破折号开头的文件名将不起作用(如果前面没有目录名)。查找它--实际上,在POSIX规范的当前版本中,支持--
是必须的。请参阅中实用程序语法指南的指南10,我对其进行了相当大的扩展,以正确处理更改目录的相对符号链接。不客气。:)||:
告诉shell即使失败也不要将该行视为错误(如果使用了set-e
,则阻止自动退出),从而允许它在支持该功能时声明东西local
,否则忽略该行。大多数/bin/sh
实现支持本地
,但POSIX标准并不要求它们。顺便说一句,| |:
只是| true
的常用习惯用法:
是true
的同义词(字面上,在大多数shell中调用完全相同的内置命令)。$(pwd)
的效率远远低于$pwd
,因为与任何$(…)
扩展一样,它分叉子shell,在该子shell中运行操作,读取其stdout,等待它退出,并替换该输出。此外,在引用时也存在错误<当path
包含空格时,code>$(basename$path)会严重中断。至少使用$(basename“$path”)
,尽管这仍然比等效的参数扩展效率低得多。此外,不寻常地使用echo
是一种糟糕的形式——echo的功能因平台而异。请参阅:某些版本的echo
(POSIX+XSI)默认情况下展开反斜杠展开;在某些情况下,反斜杠展开的行为尚未定义;处理仅包含“-n”
的字符串时的行为未定义;字符串-e
的处理是标准定义的(一致性实现必须在其输出上打印-e
),但包括bash在内的一些实现在这方面不符合标准。否则,这并不可怕。因为它总是使用ls-ld
,而不是在可用时更喜欢GNU find或perl,所以它无法正确处理名称中包含“->”的符号链接,并且链接目标中包含不可打印字符的行为可能会被破坏,但如果您只是尝试处理行为良好的文件名(幸运的是!),这可能是可以接受的。说到性能——在定义函数之前,我已经更新了我的版本,只运行了一次测试来确定可用的最佳readlink实现,而不是每次调用readlink时都这样做。