Comment résoudre les liens symboliques dans un script shell

220

É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 tout lien symbolique intermédiaire. 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 à cacher à l'utilisateur l'existence de liens symboliques (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 resolver () telle que lorsqu'elle est exécutée à partir du répertoire tmp dans l'exemple ci-dessus, resolve ("foo") == "/ Users / greg / tmp / bar".

Greg Hewgill
la source

Réponses:

92

Selon les normes, pwd -Pdevrait retourner le chemin d'accès avec les liens symboliques résolus.

La fonction C char *getcwd(char *buf, size_t size)de unistd.hdevrait avoir le même comportement.

getcwd pwd

kauppi
la source
22
Cela fonctionne juste pour le répertoire (actuel)? Si la cible est un fichier, il ne donnera rien ...
dala
4
Ne fonctionne pas si le lien est rompu car vous ne pouvez pas en faire votre chemin actuel
Tom Howard
6
Pour résumer avec le recul: Cette réponse ne fonctionne que dans des circonstances très limitées, à savoir si le lien symbolique d'intérêt est vers un répertoire qui existe réellement ; De plus, vous devez cdd'abord le faire avant d'appeler pwd -P. En d'autres termes: cela ne vous permettra pas de résoudre (voir la cible de) les liens symboliques vers des fichiers ou des liens symboliques cassés , et pour résoudre les liens symboliques des répertoires existants, vous devez effectuer un travail supplémentaire (restaurer le répertoire de travail précédent ou localiser les appels cdet pwd -Pen sous-coque).
mklement0
mauvais je cherche un moyen de résoudre un fichier et non un répertoire.
Martin
Comme d'autres l'ont souligné, cela ne répond pas vraiment à la question. La réponse de @ pixelbeat ci-dessous le fait.
erstaples
401
readlink -f "$path"

Note de l'éditeur: ce qui précède fonctionne avec GNU readlink et FreeBSD / PC-BSD / OpenBSD readlink , mais pas sous OS X à partir de 10.11.
GNU readlink offre des options supplémentaires et connexes, telles que la -mrésolution d'un lien symbolique, que la cible ultime existe ou non.

Notez que depuis GNU coreutils 8.15 (2012-01-06), il existe un programme realpath qui est moins obtus et plus flexible que ce qui précède. Il est également compatible avec l'utilisation FreeBSD du même nom. Il inclut également des fonctionnalités pour générer un chemin relatif entre deux fichiers.

realpath $path

[ Ajout admin ci-dessous d'après le commentaire de halloleo - danorton]

Pour Mac OS X (via au moins 10.11.x), utilisez readlinksans l' -foption:

readlink $path

Note de l'éditeur: cela ne résoudra pas les liens symboliques de manière récursive et ne signalera donc pas la cible ultime ; Par exemple, étant donné un lien symbolique aqui pointe vers b, qui à son tour pointe vers c, cela ne fera que rapporter b(et ne garantira pas qu'il est sorti comme un chemin absolu ).
Utilisez la perlcommande suivante sur OS X pour combler le vide de la readlink -ffonctionnalité manquante :
perl -MCwd -le 'print Cwd::abs_path(shift)' "$path"

pixelbeat
la source
5
Cela ne fonctionne pas sous Mac OS X - voir stackoverflow.com/questions/1055671/…
Bkkbrad
1
honte de l'incompatibilité d'OS X, sinon agréable +1
jkp
11
Sur OS X, vous pouvez installer coreutils avec homebrew. Il l'installe comme "grealpath".
Kief
10
readlinkfonctionne sur OSX, mais a besoin d'une autre syntaxe: readlink $path sans le -f.
halloleo du
2
readlink ne parvient pas à déréférencer plusieurs couches de lien symbolique, mais il ne dérive qu'une couche à la fois
Magnus
26

"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
tlrobinson
la source
17

Un de mes favoris est realpath foo

realpath - retourne le chemin absolu canonisé

realpath développe tous les liens symboliques et résout les références aux caractères «/./», «/../» et «/» supplémentaires dans la chaîne terminée par un nom nommé par path et
       stocke le nom de chemin absolu canonisé dans le tampon de taille PATH_MAX nommé par resolution_path. Le chemin résultant n'aura pas de lien symbolique, '/./' ou
       '/../' Composants.
Gregory
la source
Sur Debian (etch et versions ultérieures), cette commande est disponible dans le paquet realpath.
Phil Ross
2
realpath fait maintenant (janvier 2012) partie de coreutils et est rétrocompatible avec la variante debian et BSD
pixelbeat
1
Je n'ai pas realpathsur Centos 6 avec GNU coreutils 8.4.31. J'en ai rencontré plusieurs autres sur Unix et Linux qui ont un coreutils GNU emballé sans realpath . Il semble donc que cela dépende de plus que de la seule version.
toxalot
Je préfère realpathplus readlinkcar il propose des drapeaux d'options tels que--relative-to
arr_sea
10
readlink -e [filepath]

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

Chuck Kollars
la source
Je viens d'être mordu par ça. Cela ne fonctionne pas sur Mac et je cherche un remplacement.
dhill
5

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|"; } 
Keymon
la source
J'ai besoin de cd dans myreadlink (), car c'est une fonction récursive, allant dans chaque répertoire jusqu'à ce qu'il trouve un lien. S'il trouve un lien, renvoie le chemin réel, puis sed remplacerait le chemin.
Keymon
5

Assembler certaines des solutions données, sachant que readlink est disponible sur la plupart des systèmes, mais a besoin d'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* ]]d'exclure -fd'OSX uniquement.

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

Voici comment obtenir le chemin réel du fichier sous MacOS / Unix en utilisant 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 avec lien symbolique:

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

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

(cd "$DIR"; pwd -P)

Cependant, s'il peut s'agir 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 vrai chemin, afin de pouvoir référencer la configuration ou d'autres scripts installés avec lui, 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 pouvez définir SOURCEn'importe quel chemin de fichier. Fondamentalement, tant que le chemin est un lien symbolique, il résout ce lien symbolique. L'astuce est 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 DIRpour lui, qui a été résolu en un emplacement réel par la simple astuce que j'ai décrite en premier.

Daniel C. Sobral
la source
2
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"))/..
Dave
la source
Cela rompra avec (a) tout chemin contenant des espaces blancs ou des métacaractères shell et (b) des liens symboliques cassés (pas un problème avec si vous voulez juste le chemin parent du script en cours d'exécution, en supposant que (a) ne s'applique pas).
mklement0
Si vous êtes concerné par les espaces, utilisez des guillemets.
Dave
Veuillez le faire - bien que cela n'aborde pas (b).
mklement0
Veuillez me montrer un exemple de b) où cela échoue. Par définition, un lien symbolique brisé pointe vers une entrée de répertoire qui n'existe pas. Le but de ce script est de résoudre les liens symboliques dans l'autre sens. Si le lien symbolique était rompu, vous n'exécuteriez pas le script. Cet exemple est destiné à illustrer la résolution du script en cours d'exécution.
Dave
"destiné à démontrer la résolution du script en cours d'exécution" - en effet, qui est un rétrécissement de la portée de la question sur laquelle vous avez choisi de vous concentrer; c'est très bien, tant que vous le dites. Comme vous ne l'avez pas fait, je l'ai dit dans mon commentaire. Veuillez résoudre le problème de citation, qui est un problème quelle que soit la portée de la réponse.
mklement0
2

Remarque: Je pense que c'est une solution solide, portable et prête à l'emploi, qui est invariablement longue pour cette même raison.

Vous trouverez ci-dessous un script / fonction entièrement compatible POSIX qui est donc multiplateforme (fonctionne également sur macOS, qui readlinkne prend toujours pas en charge à -fpartir de 10.12 (Sierra)) - il utilise uniquement les fonctionnalités du langage shell POSIX et uniquement les appels d'utilitaires compatibles POSIX .

Il s'agit d'une implémentation portable de GNUreadlink -e (la version la plus stricte de readlink -f).

Vous pouvez exécuter le scénario avecsh ou la source de la fonction dans bash, kshetzsh :

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

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

rreadlink définition de script / fonction:

Le code a été adapté avec gratitude à partir de cette réponse .
J'ai également créé une bashversion d'utilitaire autonome basée ici , que vous pouvez installer avec
npm install rreadlink -g, si vous avez Node.js 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 assurant que la fonction intégrée commandn'est pas masquée par un alias ou une fonction shell du même nom, demande dans un commentaire:

Que faire si unaliasou unsetet [sont définis comme alias ou fonctions shell?

La motivation derrière l' rreadlinkassurance que cela commanda sa signification d'origine est de l'utiliser pour contourner les alias de commodité (bénins) et les fonctions souvent utilisées pour masquer les commandes standard dans des shells interactifs, telles que la redéfinition lspour inclure les options favorites.

Je pense qu'il est sûr de dire que si vous avez affaire à un non fiable, environnement malveillant, se soucier unaliasou unset- ou, pour cette matière, while, do, ... - redéfinition n'est pas une préoccupation.

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 de redéfinir les codes internes et même les mots clés de langage est intrinsèquement un risque pour la sécurité (et l'écriture de code paranoïaque est difficile en général).

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

La fonction repose sur unaliaset a unsetsa signification d'origine. Les redéfinir en tant que fonctions shell d'une manière qui modifie leur comportement serait un problème; la redéfinition en tant qu'alias n'est pas nécessairement une préoccupation, car le fait de citer (en partie) le nom de la commande (par exemple \unalias) contourne les alias.

Cependant, citant est pas une option pour shell mots - clés ( while, for, if, do, ...) et alors que les mots - clés shell faire préséance sur shell fonctions , dans bashet zshalias ont la plus haute priorité, afin de se prémunir contre redéfinitions shell-mot clé que vous devez exécuter unaliasavec leurs noms (bien que dans les bash shells non interactifs (tels que les scripts), les alias ne sont pas développés par défaut - uniquement s'ils shopt -s expand_aliasessont explicitement appelés en premier).

Pour vous assurer que unalias- en tant que fonction intégrée - a sa signification d'origine, vous devez d'abord l'utiliser \unsetdessus, ce qui nécessite que unsetsa signification d'origine:

unsetest un shell intégré , donc pour vous assurer qu'il est invoqué en tant que tel, vous devez vous assurer qu'il n'est pas redéfini en tant que 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 ne puissiez compter sur unsetpour avoir sa signification d'origine, d'après ce que je peux dire, il n'y a aucun moyen garanti de se défendre contre toutes les redéfinitions malveillantes.

mklement0
la source
D'après mon expérience, en citant les alias de contournement, pas les fonctions de shell, contrairement à ce que vous dites d'abord.
jarno
J'ai testé que je peux définir [comme un alias dans dashet bashet comme une fonction shell dans bash.
jarno
1
J'ai fait valoir mon argument en tant que question distincte .
jarno
@jarno: Bons points; J'ai mis à jour ma réponse; faites-moi savoir si vous pensez qu'il y a toujours un problème.
mklement0
1
@jarno: Vous pouvez définir whileen fonction bash, kshet zsh(mais pas dash), mais seulement avec la function <name>syntaxe: function while { echo foo; }œuvres ( while() { echo foo; }ne fonctionne pas). Cependant, cela n'occultera pas le while mot - clé , car les mots-clés ont une priorité plus élevée que les fonctions (la seule façon d'appeler cette fonction est as \while). Dans bashet zsh, les alias ont une priorité plus élevée que les mots-clés, donc les redéfinitions d' alias des mots-clés les masquent (mais bashpar défaut uniquement dans les shells interactifs , sauf si elles shopt -s expand_aliasessont explicitement appelées).
mklement0
1

Les scripts shell communs 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 position "réelle" à partir de 0 $ seulement.

cat `mvn`

sur mon système imprime un script contenant les éléments suivants, ce qui devrait être un bon indice de 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`
Hugo
la source
1

Essaye ça:

cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0))
bricolage
la source
1

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

La version vivante vit 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 ne sera peut-être pas difficile de le faire fonctionner pour du shell bourne ordinaire (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 -real-:

%% 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 
enthousiaste
la source
Ma réponse précédente fait exactement la même chose dans environ 1/4 de l'espace :) C'est un script shell assez basique et pas digne d'un dépôt git.
Dave
1

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

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

Pas génial mais cross OS

Clemens Tolboom
la source
3
Python 2.6+ est disponible sur plus de systèmes d'utilisateurs finaux que php, donc cela python -c "from os import path; print(path.realpath('${SYMLINK_PATH}'));"aurait probablement plus de sens. Pourtant, au moment où vous devez utiliser Python à partir d'un script shell, vous devriez probablement utiliser Python et vous épargner le casse-tête des scripts shell multiplateforme.
Jonathan Baldwin
Vous n'avez pas besoin de plus que le lien de lecture intégré sh, dirname et basename.
Dave
@ Dave: dirname, basename, et readlinksont externes utilitaires , non encastrés coque; dirnameet basenamefont partie de POSIX, readlinkne l'est pas.
mklement0
@ mklement0 - Vous avez tout à fait raison. Ils sont fournis par CoreUtils ou équivalent. Je ne devrais pas visiter SO après 1h du matin. L'essence de mon commentaire est que ni PHP ni aucun autre interpréteur de langage au-delà de celui installé dans un système de base ne sont nécessaires. J'ai utilisé le script que j'ai fourni sur cette page sur chaque variante linux depuis 1997 et MacOS X depuis 2006 sans erreur. L'OP n'a pas demandé de solution POSIX. Leur environnement spécifique était Mac OS X.
Dave
@Dave: Oui, il est possible de le faire avec des utilitaires de stock, mais c'est aussi difficile à faire (comme en témoignent les lacunes de votre script). Si OS X était vraiment au centre, alors cette réponse est parfaitement bien - et beaucoup plus simple - étant donné que phpvient avec OS X. Cependant, même si le corps de la question mentionne OS X, il n'est pas étiqueté comme tel, et c'est devenu clair que les gens sur diverses plates-formes viennent ici pour des réponses, il convient donc de souligner ce qui est spécifique à la plate-forme / non-POSIX.
mklement0
1

Il s'agit d'un résolveur de lien symbolique dans Bash qui fonctionne que le lien soit un répertoire ou un non-répertoire:

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

Notez que tout cdet settout se déroule dans un sous-shell.

solidsnack
la source
En fait, les {} autour de () ne sont pas nécessaires, car () compte comme une "commande composée", tout comme {}. Mais vous avez toujours besoin de () après le nom de la fonction.
Chris Cogdon
@ChrisCogdon Le {}autour du ()n'est pas inutile s'il n'y en a pas ()derrière le nom de la fonction. Bash accepte les déclarations de fonctions sans ()car le shell n'a pas de listes de paramètres dans les déclarations et ne fait pas d'appels avec, ()donc les déclarations de fonctions avec ()n'ont pas beaucoup de sens.
solidsnack
0

Ici, je présente ce que je pense ê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 d'origine.

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
}
RuneImp
la source
0

Ma réponse ici Bash: comment obtenir le vrai chemin d'un lien symbolique?

mais bref très pratique dans les scripts:

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

Ceux-ci font partie de coreutils GNU, adaptés à une utilisation dans les systèmes Linux.

Pour tout tester, nous mettons le lien symbolique dans / home / test2 /, modifions quelques éléments supplémentaires et l'exécutons / l'appelons depuis le répertoire racine:

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

Original script is: /home/test/realscript.sh
Called script is: /home/test2/symlink
Arunas Bartisius
la source
0

Dans le cas où pwd ne peut pas être utilisé (par exemple, appeler des scripts à partir d'un emplacement différent), utilisez realpath (avec ou sans dirname):

$(dirname $(realpath $PATH_TO_BE_RESOLVED))

Fonctionne à la fois lors de l'appel via (plusieurs) liens symboliques ou lors de l'appel direct du script - depuis n'importe quel emplacement.

nakwa
la source