Question

Étant donné un chemin absolu ou relatif (dans un système de type Unix), je voudrais déterminer le chemin complet de la cible après avoir résolu les liens symboliques intermédiaires.Points bonus pour résoudre également la notation ~ nom d'utilisateur en même temps.

Si la cible est un répertoire, il pourrait être possible de chdir() dans le répertoire puis d'appeler getcwd(), mais je veux vraiment le faire à partir d'un script shell plutôt que d'écrire un assistant C.Malheureusement, les shells ont tendance à essayer de cacher l'existence de liens symboliques à l'utilisateur (c'est bash sur OS X) :

$ ls -ld foo bar
drwxr-xr-x   2 greg  greg  68 Aug 11 22:36 bar
lrwxr-xr-x   1 greg  greg   3 Aug 11 22:36 foo -> bar
$ cd foo
$ pwd
/Users/greg/tmp/foo
$

Ce que je veux, c'est une fonction solve() telle que lorsqu'elle est exécutée à partir du répertoire tmp dans l'exemple ci-dessus, solve("foo") == "/Users/greg/tmp/bar".

Était-ce utile?

La solution

Selon les normes, pwd -P devrait renvoyer le chemin avec les liens symboliques résolus.

Fonction C char *getcwd(char *buf, size_t size) depuis unistd.h devrait avoir le même comportement.

obtenircwd mot de passe

Autres conseils

readlink -f "$path"

Note de l'éditeur:Ce qui précède fonctionne avec GNOU readlink et FreeBSD/PC-BSD/OpenBSD readlink, mais pas sur OS X à partir du 10.11.
GNOU readlink propose des options supplémentaires connexes, telles que -m pour résoudre un lien symbolique, que la cible ultime existe ou non.

Notez que depuis GNU coreutils 8.15 (06/01/2012), il existe un chemin réel programme disponible qui est moins obtus et plus flexible que le précédent.Il est également compatible avec l'utilitaire FreeBSD du même nom.Il inclut également des fonctionnalités permettant de générer un chemin relatif entre deux fichiers.

realpath $path

[Ajout de l'administrateur ci-dessous à partir du commentaire de halloléoDanorton]

Pour Mac OS X (jusqu'à au moins 10.11.x), utilisez readlink sans le -f option:

readlink $path

Note de l'éditeur:Cela ne résoudra pas les liens symboliques récursivement et ne signalera donc pas le ultime cible;par exemple, étant donné un lien symbolique a qui pointe vers b, ce qui à son tour indique c, cela ne fera que signaler b (et ne garantira pas qu'il soit affiché en tant que chemin absolu).
Utilisez le suivant perl commande sur OS X pour combler le vide des manquants readlink -f Fonctionnalité:
perl -MCwd -le 'print Cwd::abs_path(shift)' "$path"

"pwd -P" semble fonctionner si vous voulez juste le répertoire, mais si pour une raison quelconque vous voulez le nom de l'exécutable réel, je ne pense pas que cela aide.Voici ma solution :

#!/bin/bash

# get the absolute path of the executable
SELF_PATH=$(cd -P -- "$(dirname -- "$0")" && pwd -P) && SELF_PATH=$SELF_PATH/$(basename -- "$0")

# resolve symlinks
while [[ -h $SELF_PATH ]]; do
    # 1) cd to directory of the symlink
    # 2) cd to the directory of where the symlink points
    # 3) get the pwd
    # 4) append the basename
    DIR=$(dirname -- "$SELF_PATH")
    SYM=$(readlink "$SELF_PATH")
    SELF_PATH=$(cd "$DIR" && cd "$(dirname -- "$SYM")" && pwd)/$(basename -- "$SYM")
done

Un de mes favoris est 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.
readlink -e [filepath]

Semble être exactement ce que vous demandez - il accepte un chemin d'arbiraire, résout tous les liens symboliques et renvoie le "vrai" chemin - et c'est "standard * nix" que tous les systèmes ont déjà déjà

Autrement:

# 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|"; } 

En rassemblant certaines des solutions proposées, sachant que readlink est disponible sur la plupart des systèmes, mais nécessite des arguments différents, cela fonctionne bien pour moi sur OSX et Debian.Je ne suis pas sûr des systèmes BSD.Peut-être que la condition doit être [[ $OSTYPE != darwin* ]] exclure -f depuis OSX uniquement.

#!/bin/bash
MY_DIR=$( cd $(dirname $(readlink `[[ $OSTYPE == linux* ]] && echo "-f"` $0)) ; pwd -P)
echo "$MY_DIR"

Note:Je crois qu'il s'agit d'une solution solide, portable et prête à l'emploi, qui est invariablement longue pour cette raison même.

Ci-dessous se trouve un Script/fonction entièrement conforme à POSIX c'est donc multiplateforme (fonctionne également sur macOS, dont readlink ne supporte toujours pas -f à partir du 10.12 (Sierra)) - il utilise uniquement Fonctionnalités du langage shell POSIX et uniquement les appels utilitaires compatibles POSIX.

C'est un implémentation portable de GNU readlink -e (la version la plus stricte de readlink -f).

Tu peux exécuter le scénario avec sh ou sourcer le fonction dans bash, ksh, et zsh:

Par exemple, dans un script, vous pouvez l'utiliser comme suit pour obtenir le véritable répertoire d'origine du script en cours d'exécution, avec les liens symboliques résolus :

trueScriptDir=$(dirname -- "$(rreadlink "$0")")

rreadlink définition du script/fonction :

Le code a été adapté avec la gratitude de cette réponse.
J'ai également créé un bash-version utilitaire autonome basée sur ici, que vous pouvez installer avec
npm install rreadlink -g, si Node.js est installé.

#!/bin/sh

# SYNOPSIS
#   rreadlink <fileOrDirPath>
# DESCRIPTION
#   Resolves <fileOrDirPath> to its ultimate target, if it is a symlink, and
#   prints its canonical path. If it is not a symlink, its own canonical path
#   is printed.
#   A broken symlink causes an error that reports the non-existent target.
# LIMITATIONS
#   - Won't work with filenames with embedded newlines or filenames containing 
#     the string ' -> '.
# COMPATIBILITY
#   This is a fully POSIX-compliant implementation of what GNU readlink's
#    -e option does.
# EXAMPLE
#   In a shell script, use the following to get that script's true directory of origin:
#     trueScriptDir=$(dirname -- "$(rreadlink "$0")")
rreadlink() ( # Execute the function in a *subshell* to localize variables and the effect of `cd`.

  target=$1 fname= targetDir= CDPATH=

  # Try to make the execution environment as predictable as possible:
  # All commands below are invoked via `command`, so we must make sure that
  # `command` itself is not redefined as an alias or shell function.
  # (Note that command is too inconsistent across shells, so we don't use it.)
  # `command` is a *builtin* in bash, dash, ksh, zsh, and some platforms do not 
  # even have an external utility version of it (e.g, Ubuntu).
  # `command` bypasses aliases and shell functions and also finds builtins 
  # in bash, dash, and ksh. In zsh, option POSIX_BUILTINS must be turned on for
  # that to happen.
  { \unalias command; \unset -f command; } >/dev/null 2>&1
  [ -n "$ZSH_VERSION" ] && options[POSIX_BUILTINS]=on # make zsh find *builtins* with `command` too.

  while :; do # Resolve potential symlinks until the ultimate target is found.
      [ -L "$target" ] || [ -e "$target" ] || { command printf '%s\n' "ERROR: '$target' does not exist." >&2; return 1; }
      command cd "$(command dirname -- "$target")" # Change to target dir; necessary for correct resolution of target path.
      fname=$(command basename -- "$target") # Extract filename.
      [ "$fname" = '/' ] && fname='' # !! curiously, `basename /` returns '/'
      if [ -L "$fname" ]; then
        # Extract [next] target path, which may be defined
        # *relative* to the symlink's own directory.
        # Note: We parse `ls -l` output to find the symlink target
        #       which is the only POSIX-compliant, albeit somewhat fragile, way.
        target=$(command ls -l "$fname")
        target=${target#* -> }
        continue # Resolve [next] symlink target.
      fi
      break # Ultimate target reached.
  done
  targetDir=$(command pwd -P) # Get canonical dir. path
  # Output the ultimate target's canonical path.
  # Note that we manually resolve paths ending in /. and /.. to make sure we have a normalized path.
  if [ "$fname" = '.' ]; then
    command printf '%s\n' "${targetDir%/}"
  elif  [ "$fname" = '..' ]; then
    # Caveat: something like /var/.. will resolve to /private (assuming /var@ -> /private/var), i.e. the '..' is applied
    # AFTER canonicalization.
    command printf '%s\n' "$(command dirname -- "${targetDir}")"
  else
    command printf '%s\n' "${targetDir%/}/$fname"
  fi
)

rreadlink "$@"

Une tangente sur la sécurité :

jarno, en référence à la fonction garantissant que le module intégré command n'est pas masqué par un alias ou une fonction shell du même nom, demande dans un commentaire :

Et si unalias ou unset et [ sont définis comme des alias ou des fonctions shell ?

La motivation derrière rreadlink assurer que command a sa signification originelle est de l'utiliser pour contourner (bénigne) commodité alias et fonctions souvent utilisés pour masquer les commandes standard dans les shells interactifs, telles que la redéfinition ls pour inclure les options favorites.

Je pense qu'il est prudent de dire qu'à moins que vous n'ayez affaire à un environnement malveillant et non fiable, vous vous inquiétez unalias ou unset - ou, d'ailleurs, while, do,...- être redéfini n'est pas un souci.

Il y a quelque chose sur lequel la fonction doit s'appuyer pour avoir sa signification et son comportement d'origine - il n'y a aucun moyen de contourner cela.
Le fait que les shells de type POSIX permettent la redéfinition des éléments intégrés et même des mots-clés de langage est intrinsèquement un risque de sécurité (et écrire du code paranoïaque est difficile en général).

Pour répondre spécifiquement à vos préoccupations :

La fonction s'appuie sur unalias et unset ayant leur signification originelle.Les faire redéfinir comme fonctions shell d'une manière qui modifie leur comportement constituerait un problème ;redéfinition comme un alias n'est pas nécessairement une préoccupation, car citant (une partie du) nom de la commande (par exemple, \unalias) contourne les alias.

Cependant, citer est pas une option pour le shell mots clés (while, for, if, do, ...) et bien que les mots-clés shell aient la priorité sur le shell les fonctions, dans bash et zsh les alias ont la priorité la plus élevée, donc pour vous prémunir contre les redéfinitions de mots-clés shell, vous devez exécuter unalias avec leurs noms (bien que dans non interactif bash les alias des shells (tels que les scripts) sont pas développé par défaut - seulement si shopt -s expand_aliases est explicitement appelé en premier).

Pour être sur de unalias - en tant que module intégré - a sa signification originale, vous devez utiliser \unset dessus en premier, ce qui nécessite que unset a sa signification originelle :

unset est une coquille intégré, donc pour vous assurer qu'il est invoqué en tant que tel, vous devez vous assurer qu'il n'est pas lui-même redéfini comme un fonction.Bien que vous puissiez contourner un formulaire d'alias avec des guillemets, vous ne pouvez pas contourner un formulaire de fonction shell - catch 22.

Ainsi, à moins que vous puissiez compter sur unset pour avoir son sens originel, d'après ce que je peux dire, il n'existe aucun moyen garanti de se défendre contre toutes les redéfinitions malveillantes.

Les scripts shell courants doivent souvent trouver leur répertoire "home" même s'ils sont invoqués en tant que lien symbolique.Le script doit donc trouver sa « vraie » position à partir de seulement 0 $.

cat `mvn`

sur mon système, imprime un script contenant ce qui suit, ce qui devrait être un bon indice sur ce dont vous avez besoin.

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`
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"))/..

Essaye ça:

cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0))

Comme j'ai rencontré cela plusieurs fois au fil des ans, et que cette fois-ci j'avais besoin d'une version portable pure bash que je pourrais utiliser sur OSX et Linux, j'ai continué et j'en ai écrit une :

La version vivante se trouve ici :

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

mais pour le bien de SO, voici la version actuelle (je pense qu'elle est bien testée... mais je suis ouvert aux commentaires !)

Ce n'est peut-être pas difficile de le faire fonctionner pour un simple bourne shell (sh), mais je n'ai pas essayé... J'aime trop $FUNCNAME.:)

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

voici un exemple classique, grâce à 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

utilisez cette fonction et elle renverra le chemin -réel- :

%% 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 

Pour contourner l'incompatibilité Mac, j'ai trouvé

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

Pas génial mais cross OS

Voici comment obtenir le chemin réel du fichier sous MacOS/Unix à l'aide d'un script Perl en ligne :

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

De même, pour obtenir le répertoire d'un fichier lié symboliquement :

DIR=$(perl -e "use Cwd qw(abs_path); use File::Basename; print dirname(abs_path('$0'))")

Votre chemin est-il un répertoire ou s'agit-il d'un fichier ?Si c'est un répertoire, c'est simple :

(cd "$DIR"; pwd -P)

Cependant, s'il s'agit d'un fichier, cela ne fonctionnera pas :

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

car le lien symbolique peut se résoudre en un chemin relatif ou complet.

Sur les scripts, je dois trouver le chemin réel, afin de pouvoir référencer la configuration ou d'autres scripts installés avec celui-ci, j'utilise ceci :

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

Vous pourriez définir SOURCE à n’importe quel chemin de fichier.Fondamentalement, tant que le chemin est un lien symbolique, il résout ce lien symbolique.L'astuce se trouve dans la dernière ligne de la boucle.Si le lien symbolique résolu est absolu, il l'utilisera comme SOURCE.Cependant, s'il est relatif, il ajoutera le préfixe DIR pour cela, qui a été transformé en un emplacement réel par le simple truc que j'ai décrit pour la première fois.

Je pense que c'est la "façon véritable et définitive de résoudre un lien symbolique", qu'il s'agisse d'un répertoire ou d'un non-répertoire, en utilisant Bash :

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 "$link"
    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##*/}"
)}

Notez que tous les cd et set les choses se déroulent dans un sous-shell.

Je présente ici ce que je crois être une solution multiplateforme (Linux et macOS au moins) à la réponse qui fonctionne bien pour moi actuellement.

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"
}

Voici une solution macOS (uniquement ?).Peut-être mieux adapté à la question initiale.

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
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top