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

如何在bash中查找指向符号链接的符号链接,bash,Bash,我正在准备一个bash脚本来验证符号链接的正确性。我想在以下情况下进行诊断: @符号链接已断开 @symlink指向另一个@symlink--(用symlinks链的最终目标修复) @symlink指向另一个@symlink,它已断开 @符号链是一个循环 当symlink指向symlink时,我在诊断第2)点时遇到了很大的问题 我试图使用readlink,但它返回符号链接链的最终目标,而不是指向的符号链接名称。我尝试在没有-f参数的情况下运行它,但没有任何帮助。然后与find的组合给了我糟糕的结

我正在准备一个bash脚本来验证符号链接的正确性。我想在以下情况下进行诊断:

  • @符号链接已断开
  • @symlink指向另一个@symlink--(用symlinks链的最终目标修复)
  • @symlink指向另一个@symlink,它已断开
  • @符号链是一个循环
  • 当symlink指向symlink时,我在诊断第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
    
    更新

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


    从您的问题描述中不清楚如何处理传递链末端的断开链接。我将简单地报告一个错误并继续

    #!/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"
    

    我是否正确理解您希望用指向最终目标的符号链接替换指向符号链接(递归等)的符号链接?这个脚本做到了这一点,尽管有点过分,因为它将重写所有解析的符号链接;优化这一点以避免不必要的操作只是一个练习。

    我不是bash方面的专家,因此可能有人向我们展示了更简单的解决方案,但这次我分享了我的脚本(它确保符号链接的相对路径与相对链接):


    挑剔:你所有的分号都是无用的,可以删除而不会产生不良影响。(如果您在同一行中编写了
    if…;那么
    ,您才需要它们。)感谢您的提示:)当我试图在我的测试文件结构(问题中的更新部分)上运行您的脚本时,我得到了:symlinks.sh:symlinks/sym_break最终解析为不存在的目标../noexist(…)以及symlinks目录中所有6个symlinks的类似结果,当它应该只出现在那些2与“破”关键字。你知道为什么它不能按预期工作吗?注:当然,我将
    路径
    目录从您的代码更改为
    符号链接
    ,以及关于链末端断开链接的处理-您将其报告为错误的决定对我来说是完美的:)请参阅更新的答案。错误出现在相对符号链接中——原始代码将它们解释为相对于当前目录,而正确的解决方案是相对于我们正在解析的符号链接的目录解析它们。我还修复了用更新的链接覆盖指向目录的符号链接的错误。如果您需要的话,它为OS X提供了一个可移植的
    abspath
    功能。非常感谢:)您的代码和建议非常有用:)我尝试在完成后发布此脚本的扩展版本。如果能阅读你的提示和评论,那就太好了。最佳:)重复的
    local
    声明是多余的。您应该只声明一次变量
    local
    ,通常在函数的开头。
    #!/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