How to resolve symbolic links in a shell script

后端 未结 19 2118
死守一世寂寞
死守一世寂寞 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:39

    One of my favorites is realpath foo

    realpath - return the canonicalized absolute pathname
    
    realpath  expands  all  symbolic  links  and resolves references to '/./', '/../' and extra '/' characters in the null terminated string named by path and
           stores the canonicalized absolute pathname in the buffer of size PATH_MAX named by resolved_path.  The resulting path will have no symbolic link, '/./' or
           '/../' components.
    
    0 讨论(0)
  • 2020-11-28 01:39

    Is your path a directory, or might it be a file? If it's a directory, it's simple:

    (cd "$DIR"; pwd -P)
    

    However, if it might be a file, then this won't work:

    DIR=$(cd $(dirname "$FILE"); pwd -P); echo "${DIR}/$(readlink "$FILE")"
    

    because the symlink might resolve into a relative or full path.

    On scripts I need to find the real path, so that I might reference configuration or other scripts installed together with it, I use this:

    SOURCE="${BASH_SOURCE[0]}"
    while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
      DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
      SOURCE="$(readlink "$SOURCE")"
      [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
    done
    

    You could set SOURCE to any file path. Basically, for as long as the path is a symlink, it resolves that symlink. The trick is in the last line of the loop. If the resolved symlink is absolute, it will use that as SOURCE. However, if it is relative, it will prepend the DIR for it, which was resolved into a real location by the simple trick I first described.

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

    In case where pwd can't be used (e.g. calling a scripts from a different location), use realpath (with or without dirname):

    $(dirname $(realpath $PATH_TO_BE_RESOLVED))
    

    Works both when calling through (multiple) symlink(s) or when directly calling the script - from any location.

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

    Common shell scripts often have to find their "home" directory even if they are invoked as a symlink. The script thus have to find their "real" position from just $0.

    cat `mvn`
    

    on my system prints a script containing the following, which should be a good hint at what you need.

    if [ -z "$M2_HOME" ] ; then
      ## resolve links - $0 may be a link to maven's home
      PRG="$0"
    
      # need this for relative symlinks
      while [ -h "$PRG" ] ; do
        ls=`ls -ld "$PRG"`
        link=`expr "$ls" : '.*-> \(.*\)$'`
        if expr "$link" : '/.*' > /dev/null; then
          PRG="$link"
        else
          PRG="`dirname "$PRG"`/$link"
        fi
      done
    
      saveddir=`pwd`
    
      M2_HOME=`dirname "$PRG"`/..
    
      # make it fully qualified
      M2_HOME=`cd "$M2_HOME" && pwd`
    
    0 讨论(0)
  • 2020-11-28 01:44

    Since I've run into this many times over the years, and this time around I needed a pure bash portable version that I could use on OSX and linux, I went ahead and wrote one:

    The living version lives here:

    https://github.com/keen99/shell-functions/tree/master/resolve_path

    but for the sake of SO, here's the current version (I feel it's well tested..but I'm open to feedback!)

    Might not be difficult to make it work for plain bourne shell (sh), but I didn't try...I like $FUNCNAME too much. :)

    #!/bin/bash
    
    resolve_path() {
        #I'm bash only, please!
        # usage:  resolve_path <a file or directory> 
        # follows symlinks and relative paths, returns a full real path
        #
        local owd="$PWD"
        #echo "$FUNCNAME for $1" >&2
        local opath="$1"
        local npath=""
        local obase=$(basename "$opath")
        local odir=$(dirname "$opath")
        if [[ -L "$opath" ]]
        then
        #it's a link.
        #file or directory, we want to cd into it's dir
            cd $odir
        #then extract where the link points.
            npath=$(readlink "$obase")
            #have to -L BEFORE we -f, because -f includes -L :(
            if [[ -L $npath ]]
             then
            #the link points to another symlink, so go follow that.
                resolve_path "$npath"
                #and finish out early, we're done.
                return $?
                #done
            elif [[ -f $npath ]]
            #the link points to a file.
             then
                #get the dir for the new file
                nbase=$(basename $npath)
                npath=$(dirname $npath)
                cd "$npath"
                ndir=$(pwd -P)
                retval=0
                #done
            elif [[ -d $npath ]]
             then
            #the link points to a directory.
                cd "$npath"
                ndir=$(pwd -P)
                retval=0
                #done
            else
                echo "$FUNCNAME: ERROR: unknown condition inside link!!" >&2
                echo "opath [[ $opath ]]" >&2
                echo "npath [[ $npath ]]" >&2
                return 1
            fi
        else
            if ! [[ -e "$opath" ]]
             then
                echo "$FUNCNAME: $opath: No such file or directory" >&2
                return 1
                #and break early
            elif [[ -d "$opath" ]]
             then 
                cd "$opath"
                ndir=$(pwd -P)
                retval=0
                #done
            elif [[ -f "$opath" ]]
             then
                cd $odir
                ndir=$(pwd -P)
                nbase=$(basename "$opath")
                retval=0
                #done
            else
                echo "$FUNCNAME: ERROR: unknown condition outside link!!" >&2
                echo "opath [[ $opath ]]" >&2
                return 1
            fi
        fi
        #now assemble our output
        echo -n "$ndir"
        if [[ "x${nbase:=}" != "x" ]]
         then
            echo "/$nbase"
        else 
            echo
        fi
        #now return to where we were
        cd "$owd"
        return $retval
    }
    

    here's a classic example, thanks to brew:

    %% ls -l `which mvn`
    lrwxr-xr-x  1 draistrick  502  29 Dec 17 10:50 /usr/local/bin/mvn@ -> ../Cellar/maven/3.2.3/bin/mvn
    

    use this function and it will return the -real- path:

    %% cat test.sh
    #!/bin/bash
    . resolve_path.inc
    echo
    echo "relative symlinked path:"
    which mvn
    echo
    echo "and the real path:"
    resolve_path `which mvn`
    
    
    %% test.sh
    
    relative symlinked path:
    /usr/local/bin/mvn
    
    and the real path:
    /usr/local/Cellar/maven/3.2.3/libexec/bin/mvn 
    
    0 讨论(0)
  • 2020-11-28 01:46

    To work around the Mac incompatibility, I came up with

    echo `php -r "echo realpath('foo');"`
    

    Not great but cross OS

    0 讨论(0)
提交回复
热议问题