如何在bash中查找指向符号链接的符号链接

4

我正在编写一段 Bash 脚本来验证符号链接的正确性。我想要诊断以下情况:

  1. @symlink损坏了
  2. @symlink指向另一个@symlink -- (使用符号链接链的最终目标进行修复)
  3. @symlink指向另一个已损坏的@symlink
  4. @symlinks链是一个循环

在诊断符号链接指向符号链接时,我对第2点有很大问题。

我尝试使用readlink,但它返回符号链接链的最终目标而不是指向的符号链接名称。我尝试运行它而没有-f参数,但它并没有帮助。然后,使用find的组合给了我很差的结果...
有人能帮我解决这个问题吗?

下面是我当前版本的代码:

failed=0

for file in path/*
do
    if [[ -L "$file" ]]
    then
        if [[ ! -a "$file" ]]
        then
            echo "Symlink '$file' is broken -- target object doesn't exists."
            failed=1
        elif [[ -L "$(readlink -f $file)" ]]
        then
            echo "Symlink '$file' points to another symlink: '$(readlink $file)'"
            failed=1
        fi
    fi
done
exit $failed

更新

测试文件结构(其中讨论了symlinks.sh bash脚本):

**hook-tests**


1
吹毛求疵:你所有的分号都是无用的,可以删除而不会产生任何不良影响。(只有在同一行上写了 if ...; then 才需要它们。) - Jens
谢谢你的提示 :) - Hunter_71
2个回答

1

从您的问题描述中无法清楚地了解如何处理传递链末端的损坏链接。我将简单地报告错误并继续。

#!/bin/sh
rc=0
for file in path/*; do
    if [ -L "$file" ]; then
        nfile=$file
        while [ -L "$nfile" ]; do
            nfile=$(cd $(dirname "$nfile"); abspath $(readlink "$nfile"))
        done
        if ! [ -r "$nfile" ]; then
            echo "$0: $file eventually resolves to nonexistent target $nfile" >&2
            rc=1
        else
            # FIXME: maybe avoid doing this needlessly?
            rm "$file"
            ln -s "$nfile" "$file"
        fi
    fi
done
exit "$rc"

我理解您的意思是希望将符号链接替换为指向最终目标的符号链接,包括递归替换符号链接。这个脚本可以实现,但有点过度,因为它会重写所有解析的符号链接;优化以避免不必要的操作需要自行完成。


当我尝试在我的测试文件结构(问题中的UPDATE部分)上运行您的脚本时,我收到以下错误信息:symlinks.sh: symlinks/sym_broken最终解析为不存在的目标../noexist (...)对于_symlinks_目录中的所有6个符号链接都会出现类似的错误,而只应该出现带有“broken”关键字的那两个。您有任何想法为什么它不能按预期工作吗?PS. 当然,我已经将您代码中的path目录更改为symlinks - Hunter_71
关于处理链末端的断链问题 - 您决定将其报告为错误对我来说是完美的 :) - Hunter_71
请查看更新的答案。该bug出现在相对符号链接中——原始代码将它们解释为相对于当前目录,而正确的解决方法是将它们解析为正在解析的符号链接所在的目录的相对路径。我还修复了在用更新的链接覆盖到一个目录的符号链接时的bug。如果需要,在https://dev59.com/wnA65IYBdhLWcg3w5zDB#3572105上有适用于OS X的便携式abspath函数。 - tripleee
非常感谢您的代码和建议,对我非常有帮助 :) 当我完成脚本的扩展版本后,我会尝试发布它。届时能够阅读到您的建议和评论将是非常不错的。最好的祝福 :) - Hunter_71

0

我不是bash的专家,所以有可能会有人给出更简单的解决方案,但是在这个时候,我分享一下我的脚本(它确保相对链接的符号链接使用相对路径):

#!/bin/sh
# The task is to create bash script, which validates symlinks and reports when:
# (1) - symlink is broken
# (2) - symlink target point is broken
# (3) - symlink points to another symlink
# (4) - symlinks chain is a cycle

FAILED=0
FIX_SYMLINKS=false
DIR_UP="../"


function file_under_symlink_absolute_path {
    local file_under_symlink="$(readlink $1)"
    local file_dirname="$(dirname $1)"

    if [[ "$file_under_symlink" == *"$DIR_UP"* ]]; then
        if [[ "$file_under_symlink" == *"$DIR_UP$DIR_UP"* ]]; then
            while [[ "$file_under_symlink" == *"$DIR_UP"* ]]; do
                local file_under_symlink="${file_under_symlink#$DIR_UP}"

                if [[ "$file_dirname" == *"/"* ]]; then
                    local file_dirname="${file_dirname%/*}"
                else
                    local file_dirname=""
                fi
            done

            if [[ "$file_dirname" != "" ]]; then
                local file_dirname="$file_dirname/$file_under_symlink"
            else
                local file_dirname="$file_under_symlink"
            fi
        else
            if [[ "$file_dirname" == *"/"* ]]; then
                local file_dirname="${file_dirname%/*}/${file_under_symlink#$DIR_UP}"
            else
                local file_dirname="${file_under_symlink#$DIR_UP}"
            fi
        fi
    else
        local file_dirname="$file_dirname/$file_under_symlink"
    fi

    echo "$(pwd)/$file_dirname"
}

function file_under_symlink_relative_path {
    local file_dirname="$(dirname $1)"
    local symlink_target="$2"
    local file_under_symlink="$3"

    if [[ "$symlink_target" == *"$file_dirname"* ]]; then
        local prefix2cut="$(pwd)/$file_dirname"
        local target="${symlink_target##$prefix2cut}"

    else
        local symlink_target_dirname="$(dirname $symlink_target)"
        local symlink_target_basename="$(basename $symlink_target)"

        if [[ "$file_under_symlink" == "$symlink_target_dirname"* ]]; then
            local level_diff="${file_under_symlink#$symlink_target_dirname/}"
            local target="$symlink_target_basename"

            while [[ "$level_diff" == *"/"* ]]; do
                local level_diff="${level_diff%/*}"
                local target="$DIR_UP$target"
            done
        else
            if [[ "$file_dirname" == *"/"* ]]; then
                local prefix2cut="$(pwd)/${file_dirname%/*}/"
            else
                local prefix2cut="$(pwd)/"
            fi
            local target="$DIR_UP${symlink_target#$prefix2cut}"
        fi
    fi

    echo "$target"
}

function valid_symlinks {
    local current_dir="$1"

    for file in "$current_dir"/*; do
        if [[ -d "$file" ]]; then
            valid_symlinks "$file"

        elif [[ -L "$file" ]]; then
            local symlink_target=$(readlink -e "$file")
            local file_under_symlink_abs="$(file_under_symlink_absolute_path $file)"

            # reports (1), (2), (4)
            if [[ ! -a "$file" ]]; then
                echo "BROKEN   Symlink '$file' is broken, target object doesn't exists."
                FAILED=1

            # reports (3)
            # it happends when file under symlink is not the symlink target file
            elif [[ "$file_under_symlink_abs" != "$symlink_target" ]]; then
                if $FIX_SYMLINKS && [[ -r "$symlink_target" ]]; then
                    local target="$(file_under_symlink_relative_path $file $symlink_target $file_under_symlink_abs)"

                    echo "Symlink '$file' points to another symlink. It will be replace with direct symlink to target '$target'."
                    ln -fs "$target" "$file"
                else
                    local target="${file_under_symlink_abs#$(pwd)/}"

                    echo "Symlink '$file' points to another symlink '$target'."
                    FAILED=1
                fi
            fi
        fi
    done
}

if [[ -d "$1" ]]; then
    start_point=${1#$(pwd)/}
    start_point=${start_point%/}

    valid_symlinks "$start_point"
else
    echo "ERROR: You have to specify the start location path."
    FAILED=1
fi

exit $FAILED

1
重复的“local”声明是多余的。你应该只在函数的开头声明一个变量“local”。 - tripleee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接