How to resolve symbolic links in a shell script

后端 未结 19 2119
死守一世寂寞
死守一世寂寞 2020-11-28 00:48

Given an absolute or relative path (in a Unix-like system), I would like to determine the full path of the target after resolving any intermediate symlinks. Bonus points for

相关标签:
19条回答
  • 2020-11-28 01:32

    Here I present what I believe to be a cross-platform (Linux and macOS at least) solution to the answer that is working well for me currently.

    crosspath()
    {
        local ref="$1"
        if [ -x "$(which realpath)" ]; then
            path="$(realpath "$ref")"
        else
            path="$(readlink -f "$ref" 2> /dev/null)"
            if [ $? -gt 0 ]; then
                if [ -x "$(which readlink)" ]; then
                    if [ ! -z "$(readlink "$ref")" ]; then
                        ref="$(readlink "$ref")"
                    fi
                else
                    echo "realpath and readlink not available. The following may not be the final path." 1>&2
                fi
                if [ -d "$ref" ]; then
                    path="$(cd "$ref"; pwd -P)"
                else
                    path="$(cd $(dirname "$ref"); pwd -P)/$(basename "$ref")"
                fi
            fi
        fi
        echo "$path"
    }
    

    Here is a macOS (only?) solution. Possibly better suited to the original question.

    mac_realpath()
    {
        local ref="$1"
        if [[ ! -z "$(readlink "$ref")" ]]; then
            ref="$(readlink "$1")"
        fi
        if [[ -d "$ref" ]]; then
            echo "$(cd "$ref"; pwd -P)"
        else
            echo "$(cd $(dirname "$ref"); pwd -P)/$(basename "$ref")"
        fi
    }
    
    0 讨论(0)
  • 2020-11-28 01:33

    This is a symlink resolver in Bash that works whether the link is a directory or a non-directory:

    function readlinks {(
      set -o errexit -o nounset
      declare n=0 limit=1024 link="$1"
    
      # If it's a directory, just skip all this.
      if cd "$link" 2>/dev/null
      then
        pwd -P
        return 0
      fi
    
      # Resolve until we are out of links (or recurse too deep).
      while [[ -L $link ]] && [[ $n -lt $limit ]]
      do
        cd "$(dirname -- "$link")"
        n=$((n + 1))
        link="$(readlink -- "${link##*/}")"
      done
      cd "$(dirname -- "$link")"
    
      if [[ $n -ge $limit ]]
      then
        echo "Recursion limit ($limit) exceeded." >&2
        return 2
      fi
    
      printf '%s/%s\n' "$(pwd -P)" "${link##*/}"
    )}
    

    Note that all the cd and set stuff takes place in a subshell.

    0 讨论(0)
  • 2020-11-28 01:33

    My answer here Bash: how to get real path of a symlink?

    but in short very handy in scripts:

    script_home=$( dirname $(realpath "$0") )
    echo Original script home: $script_home
    

    These are part of GNU coreutils, suitable for use in Linux systems.

    To test everything, we put symlink into /home/test2/, amend some additional things and run/call it from root directory:

    /$ /home/test2/symlink
    /home/test
    Original script home: /home/test
    

    Where

    Original script is: /home/test/realscript.sh
    Called script is: /home/test2/symlink
    
    0 讨论(0)
  • 2020-11-28 01:36

    Another way:

    # Gets the real path of a link, following all links
    myreadlink() { [ ! -h "$1" ] && echo "$1" || (local link="$(expr "$(command ls -ld -- "$1")" : '.*-> \(.*\)$')"; cd $(dirname $1); myreadlink "$link" | sed "s|^\([^/].*\)\$|$(dirname $1)/\1|"); }
    
    # Returns the absolute path to a command, maybe in $PATH (which) or not. If not found, returns the same
    whereis() { echo $1 | sed "s|^\([^/].*/.*\)|$(pwd)/\1|;s|^\([^/]*\)$|$(which -- $1)|;s|^$|$1|"; } 
    
    # Returns the realpath of a called command.
    whereis_realpath() { local SCRIPT_PATH=$(whereis $1); myreadlink ${SCRIPT_PATH} | sed "s|^\([^/].*\)\$|$(dirname ${SCRIPT_PATH})/\1|"; } 
    
    0 讨论(0)
  • 2020-11-28 01:36

    Here's how one can get the actual path to the file in MacOS/Unix using an inline Perl script:

    FILE=$(perl -e "use Cwd qw(abs_path); print abs_path('$0')")
    

    Similarly, to get the directory of a symlinked file:

    DIR=$(perl -e "use Cwd qw(abs_path); use File::Basename; print dirname(abs_path('$0'))")
    
    0 讨论(0)
  • 2020-11-28 01:38
    function realpath {
        local r=$1; local t=$(readlink $r)
        while [ $t ]; do
            r=$(cd $(dirname $r) && cd $(dirname $t) && pwd -P)/$(basename $t)
            t=$(readlink $r)
        done
        echo $r
    }
    
    #example usage
    SCRIPT_PARENT_DIR=$(dirname $(realpath "$0"))/..
    
    0 讨论(0)
提交回复
热议问题