Comment normaliser un chemin de fichier dans Bash?

204

Je veux me transformer /foo/bar/..en/foo

Existe-t-il une commande bash qui fait cela?


Edit: dans mon cas pratique, le répertoire existe.

Fabien
la source
4
Est-ce important /foo/barou même d' /fooexister réellement, ou êtes-vous uniquement intéressé par l'aspect de manipulation de chaîne selon les règles de nom de chemin?
Chen Levy
5
@twalberg ... c'est un peu artificiel ...
Camilo Martin
2
@CamiloMartin Pas arrangea du tout - il fait exactement ce que la question demande - se transforme /foo/bar/..à /foo, et en utilisant une bashcommande. S'il y a d'autres exigences qui ne sont pas énoncées, alors peut-être qu'elles devraient être ...
twalberg
14
@twalberg Vous en avez fait trop TDD -_- '
Camilo Martin

Réponses:

193

si vous voulez couper une partie d'un nom de fichier à partir du chemin, "dirname" et "basename" sont vos amis, et "realpath" est également pratique.

dirname /foo/bar/baz 
# /foo/bar 
basename /foo/bar/baz
# baz
dirname $( dirname  /foo/bar/baz  ) 
# /foo 
realpath ../foo
# ../foo: No such file or directory
realpath /tmp/../tmp/../tmp
# /tmp

realpath alternatives

Si realpathn'est pas supporté par votre shell, vous pouvez essayer

readlink -f /path/here/.. 

Aussi

readlink -m /path/there/../../ 

Fonctionne de la même manière que

realpath -s /path/here/../../

en ce que le chemin n'a pas besoin d'exister pour être normalisé.

Kent Fredric
la source
5
Pour ceux d'entre vous qui ont besoin d'une solution OS X, consultez la réponse d'Adam Liss ci-dessous.
Trenton
stackoverflow.com/a/17744637/999943 Ceci est une réponse fortement liée! Je suis tombé sur ces deux postes QA en une journée et je voulais les lier ensemble.
phyatt
realpath semble avoir été ajouté à coreutils en 2012. Voir l'historique des fichiers sur github.com/coreutils/coreutils/commits/master/src/realpath.c .
Noah Lavine
1
Les deux realpathet readlinkproviennent des utilitaires de base GNU, donc vous obtenez probablement les deux ou aucun. Si je me souviens bien, la version de readlink sur Mac est légèrement différente de celle de GNU: - \
Antoine 'hashar' Musso
100

Je ne sais pas s'il existe une commande bash directe pour le faire, mais je le fais habituellement

normalDir="`cd "${dirToNormalize}";pwd`"
echo "${normalDir}"

et ça marche bien.

Tim Whitcomb
la source
9
Cela normalisera mais ne résoudra pas les liens logiciels. Cela peut être un bug ou une fonctionnalité. :-)
Adam Liss
4
Il a également un problème si $ CDPATH est défini; parce que le "cd foo" basculera dans n'importe quel répertoire "foo" qui est un sous-répertoire de $ CDPATH, pas seulement un "foo" qui se trouve dans le répertoire courant. Je pense que vous devez faire quelque chose comme: CDPATH = "" cd "$ {dirToNormalize}" && pwd -P.
mjs
7
La réponse de Tim est certainement la plus simple et la plus portable. CDPATH est facile à gérer: dir = "$ (unset CDPATH && cd" $ dir "&& pwd)"
David Blevins
2
Cela pourrait être très dangereux ( rm -rf $normalDir) si dirToNormalize n'existe pas!
Frank Meulenaar
4
Ouais, il est probablement préférable d'utiliser un &&selon le commentaire de @ DavidBlevins.
Elias Dorneles
54

Essayez realpath. Vous trouverez ci-dessous la source dans son intégralité, par la présente donnée au domaine public.

// realpath.c: display the absolute path to a file or directory.
// Adam Liss, August, 2007
// This program is provided "as-is" to the public domain, without express or
// implied warranty, for any non-profit use, provided this notice is maintained.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libgen.h>   
#include <limits.h>

static char *s_pMyName;
void usage(void);

int main(int argc, char *argv[])
{
    char
        sPath[PATH_MAX];


    s_pMyName = strdup(basename(argv[0]));

    if (argc < 2)
        usage();

    printf("%s\n", realpath(argv[1], sPath));
    return 0;
}    

void usage(void)
{
    fprintf(stderr, "usage: %s PATH\n", s_pMyName);
    exit(1);
}
Adam Liss
la source
1
Est-ce inclus dans le cadre de l'installation bash standard? Je reçois "commande introuvable" sur notre système (bash 3.00.15 (1) sur RHEL)
Tim Whitcomb
4
gnu.org/software/coreutils pour readlink, mais realpath vient de ce paquet: packages.debian.org/unstable/utils/realpath
Kent Fredric
8
C'est standard sur Ubuntu / BSD, pas Centos / OSX
Erik Aronesty
4
Vous devriez vraiment parcourir la partie avec "Bonus: c'est une commande bash, et disponible partout!" partie sur realpath. Il se trouve que c'est aussi mon préféré, mais au lieu de compléter votre outil à partir de la source. Tout est trop lourd, et la plupart du temps vous voulez juste readlink -f... BTW, readlinkn'est PAS non plus un bash intégré, mais fait partie d' coreutilsUbuntu.
Tomasz Gandor
4
Vous avez dit que vous donniez cela au domaine public, mais l'en-tête indique également "pour toute utilisation à but non lucratif" - voulez-vous vraiment le faire don au domaine public? Cela signifierait qu'il peut être utilisé à des fins commerciales à but lucratif ainsi qu'à des fins non lucratives…
me_and
36

Une solution portable et fiable consiste à utiliser python, qui est préinstallé à peu près partout (y compris Darwin). Vous avez deux options:

  1. abspath renvoie un chemin absolu mais ne résout pas les liens symboliques:

    python -c "import os,sys; print(os.path.abspath(sys.argv[1]))" path/to/file

  2. realpath renvoie un chemin absolu et résout ainsi les liens symboliques, générant un chemin canonique:

    python -c "import os,sys; print(os.path.realpath(sys.argv[1]))" path/to/file

Dans chaque cas, il path/to/filepeut s'agir d'un chemin relatif ou absolu.

Loevborg
la source
7
Merci, c'était le seul qui fonctionnait. readlink ou realpath ne sont pas disponibles sous OS X. Python devrait être sur la plupart des plates-formes.
sorin
1
Juste pour clarifier, readlinkest disponible sur OS X, mais pas avec l' -foption. Solutions de contournement portables discutées ici .
StvnW
3
Cela me dérange littéralement que c'est la seule solution sensée si vous ne voulez pas suivre les liens. La façon Unix mon PIED.
DannoHung
34

Utilisez l'utilitaire readlink du package coreutils.

MY_PATH=$(readlink -f "$0")
mattalxndr
la source
1
BSD n'a pas le drapeau -f, ce qui signifie que cela échouera même sur le dernier MacOS Mojave et de nombreux autres systèmes. Oubliez d'utiliser -f si vous voulez la portabilité, de nombreux systèmes d'exploitation sont affectés.
sorin
1
@sorin. La question ne concerne pas Mac, c'est Linux.
mattalxndr
14

readlinkest la norme bash pour obtenir le chemin absolu. Il a également l'avantage de renvoyer des chaînes vides si des chemins ou un chemin n'existe pas (étant donné les drapeaux pour le faire).

Pour obtenir le chemin absolu vers un répertoire qui peut exister ou non, mais dont les parents existent, utilisez:

abspath=$(readlink -f $path)

Pour obtenir le chemin absolu vers un répertoire qui doit exister avec tous les parents:

abspath=$(readlink -e $path)

Pour canoniser le chemin donné et suivre les liens symboliques s'ils existent, mais sinon ignorer les répertoires manquants et retourner le chemin quand même, c'est:

abspath=$(readlink -m $path)

Le seul inconvénient est que readlink suivra les liens. Si vous ne souhaitez pas suivre les liens, vous pouvez utiliser cette convention alternative:

abspath=$(cd ${path%/*} && echo $PWD/${path##*/})

Cela va chdir vers la partie répertoire de $ path et afficher le répertoire courant avec la partie fichier de $ path. S'il échoue à chdir, vous obtenez une chaîne vide et une erreur sur stderr.

Craig
la source
7
readlinkest une bonne option si elle est disponible. La version OS X ne prend pas en charge les options -eou -f. Dans les trois premiers exemples, vous devez avoir des guillemets $pathpour gérer les espaces ou les caractères génériques dans le nom de fichier. +1 pour l'extension des paramètres, mais cela présente une vulnérabilité de sécurité. Si pathest vide, ce sera cddans votre répertoire personnel. Vous avez besoin de guillemets doubles. abspath=$(cd "${path%/*}" && echo "$PWD/${path##*/}")
toxalot
Ce n'était qu'un exemple. Si vous êtes déterminé à la sécurité, vous ne devriez vraiment pas utiliser bash ou toute autre variante de shell. En outre, bash a ses propres problèmes en ce qui concerne la compatibilité entre plates-formes, ainsi que les problèmes de changement de fonctionnalité entre les principales versions. OSX n'est qu'une des nombreuses plates-formes avec des problèmes liés aux scripts shell, sans oublier qu'elle est basée sur BSD. Lorsque vous devez être vraiment multi-plateforme, vous devez être conforme à POSIX, donc l'expansion des paramètres sort vraiment de la fenêtre. Jetez un œil à Solaris ou à HP-UX.
Craig
6
Il ne s'agit pas ici d'une infraction, mais il est important de signaler des problèmes obscurs comme celui-ci. Je veux juste une réponse rapide à ce problème trivial et j'aurais fait confiance à ce code avec n'importe quelle entrée / s'il n'y avait pas eu le commentaire ci-dessus. Il est également important de prendre en charge OS-X dans ces discussions bash. Il existe de nombreuses commandes qui ne sont malheureusement pas prises en charge sur OS-X, et de nombreux forums tiennent cela pour acquis lorsque nous discutons de Bash, ce qui signifie que nous continuerons à rencontrer de nombreux problèmes multiplates-formes à moins qu'il ne soit traité plus tôt que tard.
Rebs
13

Ancienne question, mais il existe un moyen beaucoup plus simple si vous traitez des noms de chemin complets au niveau du shell:

   abspath = "$ (cd" $ path "&& pwd)"

Comme le cd se produit dans un sous-shell, il n'a pas d'impact sur le script principal.

Deux variantes, en supposant que vos commandes intégrées au shell acceptent -L et -P, sont:

   abspath = "$ (cd -P" $ path "&& pwd -P)" # chemin physique avec liens symboliques résolus
   abspath = "$ (cd -L" $ path "&& pwd -L)" # chemin logique préservant les liens symboliques

Personnellement, j'ai rarement besoin de cette approche ultérieure, sauf si je suis fasciné par les liens symboliques pour une raison quelconque.

FYI: variation sur l'obtention du répertoire de départ d'un script qui fonctionne même si le script change son répertoire actuel plus tard.

name0 = "$ (nom de base" $ 0 ")"; #base nom du script
dir0 = "$ (cd" $ (dirname "$ 0") "&& pwd)"; #absolute dir de départ

L'utilisation de CD garantit que vous avez toujours le répertoire absolu, même si le script est exécuté par des commandes telles que ./script.sh qui, sans cd / pwd, donne souvent juste .. Inutile si le script fait un cd plus tard.

Gilbert
la source
8

Comme l'a noté Adam Liss, il realpathn'est pas fourni avec chaque distribution. C'est dommage car c'est la meilleure solution. Le code source fourni est génial, et je vais probablement commencer à l'utiliser maintenant. Voici ce que j'ai utilisé jusqu'à présent, que je partage ici juste pour être complet:

get_abs_path() {
     local PARENT_DIR=$(dirname "$1")
     cd "$PARENT_DIR"
     local ABS_PATH="$(pwd)"/"$(basename "$1")"
     cd - >/dev/null
     echo "$ABS_PATH"
} 

Si vous voulez qu'il résout les liens symboliques, remplacez simplement pwdpar pwd -P.

Jeet
la source
Un piège avec l' pwd -Poption pour ce cas ... Réfléchissez à ce qui se passerait s'il y $(basename "$1")avait un lien symbolique vers un fichier dans un autre répertoire. Le pwd -Pseul résout les liens symboliques dans la partie répertoire du chemin, mais pas la partie nom de base.
toxalot
7

Ma solution récente était:

pushd foo/bar/..
dir=`pwd`
popd

Basé sur la réponse de Tim Whitcomb.

schmunk
la source
Je soupçonne que cela échoue si l'argument n'est pas un répertoire. Supposons que je veuille savoir où mène / usr / bin / java?
Edward Falk
1
Si vous savez que c'est un fichier, vous pouvez pushd $(dirname /usr/bin/java)essayer.
schmunk
5

Pas exactement une réponse mais peut-être une question de suivi (la question d'origine n'était pas explicite):

readlinkest bien si vous voulez réellement suivre les liens symboliques. Mais il existe également un cas d'utilisation pour simplement normaliser ./et ../et //séquences, ce qui peut être fait de manière purement syntaxique, sans canoniser les liens symboliques. readlinkn'est pas bon pour cela, et non plus realpath.

for f in $paths; do (cd $f; pwd); done

fonctionne pour les chemins existants, mais rompt pour les autres.

Un sedscript semble être un bon pari, sauf que vous ne pouvez pas remplacer de manière itérative des séquences ( /foo/bar/baz/../..-> /foo/bar/..-> /foo) sans utiliser quelque chose comme Perl, ce qui n'est pas sûr à assumer sur tous les systèmes, ou en utilisant une boucle laide pour comparer la sortie de sedto son entrée.

FWIW, un monoligne utilisant Java (JDK 6+):

jrunscript -e 'for (var i = 0; i < arguments.length; i++) {println(new java.io.File(new java.io.File(arguments[i]).toURI().normalize()))}' $paths
Jesse Glick
la source
realpatha une -spossibilité de ne pas résoudre les liens symboliques et que les références à résoudre /./, /../et de supprimer supplémentaires /caractères. Lorsqu'il est combiné avec l' -moption, realpathne fonctionne que sur le nom du fichier et ne touche aucun fichier réel. Cela ressemble à la solution parfaite. Mais hélas, realpathil manque toujours sur de nombreux systèmes.
toxalot
La suppression de ..composants ne peut pas être effectuée syntaxiquement lorsque des liens symboliques sont impliqués. /one/two/../threen'est pas le même que /one/threesi twoest un lien symbolique vers /foo/bar.
jrw32982 prend en charge Monica
@ jrw32982 oui, comme je l'ai dit dans ma réponse, c'est pour un cas d'utilisation où la canonisation de lien symbolique n'est pas souhaitée ou nécessaire.
Jesse Glick
@JesseGlick ce n'est pas seulement une question de savoir si vous voulez ou non canoniser les liens symboliques. Votre algorithme produit en fait la mauvaise réponse. Pour que votre réponse soit correcte, vous devez savoir a priori qu'il n'y a pas de liens symboliques impliqués (ou qu'ils n'ont qu'une certaine forme). Votre réponse indique que vous ne voulez pas les canoniser, pas qu'il n'y a pas de liens symboliques impliqués dans le chemin.
jrw32982 prend en charge Monica
Il existe des cas d'utilisation où la normalisation doit être effectuée sans supposer de structure de répertoire existante fixe. La normalisation de l'URI est similaire. Dans ces cas, c'est une limitation inhérente que le résultat ne sera généralement pas correct s'il se trouve des liens symboliques près d'un répertoire où le résultat est appliqué ultérieurement.
Jesse Glick
5

Je suis en retard à la fête, mais c'est la solution que j'ai élaborée après avoir lu un tas de fils comme celui-ci:

resolve_dir() {
        (builtin cd `dirname "${1/#~/$HOME}"`'/'`basename "${1/#~/$HOME}"` 2>/dev/null; if [ $? -eq 0 ]; then pwd; fi)
}

Cela résoudra le chemin absolu de $ 1, jouera bien avec ~, gardera les liens symboliques dans le chemin où ils se trouvent, et cela ne gâchera pas votre pile de répertoires. Il retourne le chemin complet ou rien s'il n'existe pas. Il s'attend à ce que $ 1 soit un répertoire et échouera probablement si ce n'est pas le cas, mais c'est une vérification facile à faire vous-même.

apottere
la source
4

Réponse bavarde et un peu tardive. Je dois en écrire un car je suis bloqué sur un RHEL4 / 5 plus ancien. Je gère les liens absolus et relatifs et simplifie les entrées //, /./ et somedir /../.

test -x /usr/bin/readlink || readlink () {
        echo $(/bin/ls -l $1 | /bin/cut -d'>' -f 2)
    }


test -x /usr/bin/realpath || realpath () {
    local PATH=/bin:/usr/bin
    local inputpath=$1
    local changemade=1
    while [ $changemade -ne 0 ]
    do
        changemade=0
        local realpath=""
        local token=
        for token in ${inputpath//\// }
        do 
            case $token in
            ""|".") # noop
                ;;
            "..") # up one directory
                changemade=1
                realpath=$(dirname $realpath)
                ;;
            *)
                if [ -h $realpath/$token ] 
                then
                    changemade=1
                    target=`readlink $realpath/$token`
                    if [ "${target:0:1}" = '/' ]
                    then
                        realpath=$target
                    else
                        realpath="$realpath/$target"
                    fi
                else
                    realpath="$realpath/$token"
                fi
                ;;
            esac
        done
        inputpath=$realpath
    done
    echo $realpath
}

mkdir -p /tmp/bar
(cd /tmp ; ln -s /tmp/bar foo; ln -s ../.././usr /tmp/bar/link2usr)
echo `realpath /tmp/foo`
alhernau
la source
3

Essayez notre nouveau produit de bibliothèque Bash realpath-lib que nous avons placé sur GitHub pour une utilisation gratuite et sans encombre. Il est parfaitement documenté et constitue un excellent outil d'apprentissage.

Il résout les chemins locaux, relatifs et absolus et n'a aucune dépendance sauf Bash 4+; il devrait donc fonctionner à peu près partout. C'est gratuit, propre, simple et instructif.

Tu peux faire:

get_realpath <absolute|relative|symlink|local file path>

Cette fonction est au cœur de la bibliothèque:

function get_realpath() {

if [[ -f "$1" ]]
then 
    # file *must* exist
    if cd "$(echo "${1%/*}")" &>/dev/null
    then 
        # file *may* not be local
        # exception is ./file.ext
        # try 'cd .; cd -;' *works!*
        local tmppwd="$PWD"
        cd - &>/dev/null
    else 
        # file *must* be local
        local tmppwd="$PWD"
    fi
else 
    # file *cannot* exist
    return 1 # failure
fi

# reassemble realpath
echo "$tmppwd"/"${1##*/}"
return 0 # success

}

Il contient également des fonctions pour get_dirname, get_filename, get_ stemname et validate_path. Essayez-le sur toutes les plateformes et aidez-le à l'améliorer.

AsymLabs
la source
2

Le problème realpathest qu'il n'est pas disponible sur BSD (ou OSX d'ailleurs). Voici une recette simple extraite d' un article assez ancien (2009) du Linux Journal , qui est assez portable:

function normpath() {
  # Remove all /./ sequences.
  local path=${1//\/.\//\/}

  # Remove dir/.. sequences.
  while [[ $path =~ ([^/][^/]*/\.\./) ]]; do
    path=${path/${BASH_REMATCH[0]}/}
  done
  echo $path
}

Notez que cette variante ne nécessite pas non plus que le chemin existe.

André Anjos
la source
Cependant, cela ne résout pas les liens symboliques. Realpath traite les chemins commençant à la racine et suit les liens symboliques au fur et à mesure de leur progression. Tout cela ne fait que réduire les références parentales.
Martijn Pieters
2

Sur la base de la réponse de @ Andre, je pourrais avoir une version légèrement meilleure, au cas où quelqu'un chercherait une solution sans boucle, entièrement basée sur la manipulation de chaînes. Il est également utile pour ceux qui ne veulent pas déréférencer les liens symboliques, ce qui est l'inconvénient d'utiliser realpathou readlink -f.

Il fonctionne sur les versions bash 3.2.25 et supérieures.

shopt -s extglob

normalise_path() {
    local path="$1"
    # get rid of /../ example: /one/../two to /two
    path="${path//\/*([!\/])\/\.\./}"
    # get rid of /./ and //* example: /one/.///two to /one/two
    path="${path//@(\/\.\/|\/+(\/))//}"
    # remove the last '/.'
    echo "${path%%/.}"
}

$ normalise_path /home/codemedic/../codemedic////.config
/home/codemedic/.config
ϹοδεMεδιϲ
la source
C'est une bonne idée, mais j'ai perdu 20 minutes à essayer de le faire fonctionner sur différentes versions de bash. Il s'avère que l'option shell extglob doit être activée pour que cela fonctionne, et ce n'est pas le cas par défaut. En ce qui concerne la fonctionnalité bash, il est important de spécifier à la fois la version requise et les options non par défaut car ces détails peuvent varier selon les systèmes d'exploitation. Par exemple, une version récente de Mac OSX (Yosemite) n'est livrée qu'avec une version obsolète de bash (3.2).
drwatsoncode
Désolé @ricovox; Je les ai maintenant mis à jour. J'ai hâte de connaître la version exacte de Bash que vous avez là-bas. La formule ci-dessus (mise à jour) fonctionne sur CentOS 5.8 qui est fourni avec bash 3.2.25
ϹοδεMεδιϲ
Désolé pour la confusion. Ce code a fonctionné sur ma version de Mac OSX bash (3.2.57) une fois que j'avais activé extglob. Ma note sur les versions bash était plus générale (qui s'applique en fait plus à une autre réponse ici concernant l'expression régulière dans bash).
drwatsoncode
2
J'apprécie votre réponse cependant. Je l'ai utilisé comme base pour moi. Par ailleurs, j'ai remarqué plusieurs cas où le vôtre échoue: (1) Chemins relatifs hello/../world(2) Points dans le nom de fichier /hello/..world(3) Points après une double barre oblique /hello//../world(4) Point avant ou après une double barre oblique /hello//./worldou /hello/.//world (5) Parent après le courant: /hello/./../world/ (6) Parent après Parent:, /hello/../../worldetc. - Certains d'entre eux peuvent être corrigés en utilisant une boucle pour effectuer des corrections jusqu'à ce que le chemin cesse de changer. (Retirez également dir/../, /dir/..mais supprimez dir/..de la fin.)
drwatsoncode
0

Basé sur l'excellent extrait de python de loveborg, j'ai écrit ceci:

#!/bin/sh

# Version of readlink that follows links to the end; good for Mac OS X

for file in "$@"; do
  while [ -h "$file" ]; do
    l=`readlink $file`
    case "$l" in
      /*) file="$l";;
      *) file=`dirname "$file"`/"$l"
    esac
  done
  #echo $file
  python -c "import os,sys; print os.path.abspath(sys.argv[1])" "$file"
done
Edward Falk
la source
0
FILEPATH="file.txt"
echo $(realpath $(dirname $FILEPATH))/$(basename $FILEPATH)

Cela fonctionne même si le fichier n'existe pas. Il nécessite que le répertoire contenant le fichier existe.

user240515
la source
GNU Realpath ne nécessite pas non plus que le dernier élément du chemin existe, sauf si vous utilisezrealpath -e
Martijn Pieters
0

J'avais besoin d'une solution qui ferait les trois:

  • Travaillez sur un Mac d'origine. realpathet readlink -fsont des addons
  • Résoudre les liens symboliques
  • Avoir une gestion des erreurs

Aucune des réponses n'avait à la fois # 1 et # 2. J'ai ajouté le numéro 3 pour sauver les autres rasages de yak.

#!/bin/bash

P="${1?Specify a file path}"

[ -e "$P" ] || { echo "File does not exist: $P"; exit 1; }

while [ -h "$P" ] ; do
    ls="$(ls -ld "$P")"
    link="$(expr "$ls" : '.*-> \(.*\)$')"
    expr "$link" : '/.*' > /dev/null &&
        P="$link" ||
        P="$(dirname "$P")/$link"
done
echo "$(cd "$(dirname "$P")"; pwd)/$(basename "$P")"

Voici un cas de test court avec quelques espaces tordus dans les chemins pour exercer pleinement la citation

mkdir -p "/tmp/test/ first path "
mkdir -p "/tmp/test/ second path "
echo "hello" > "/tmp/test/ first path / red .txt "
ln -s "/tmp/test/ first path / red .txt " "/tmp/test/ second path / green .txt "

cd  "/tmp/test/ second path "
fullpath " green .txt "
cat " green .txt "
David Blevins
la source
0

Je sais que c'est une question ancienne. J'offre toujours une alternative. Récemment, j'ai rencontré le même problème et je n'ai trouvé aucune commande existante et portable pour le faire. J'ai donc écrit le script shell suivant qui comprend une fonction qui peut faire l'affaire.

#! /bin/sh                                                                                                                                                

function normalize {
  local rc=0
  local ret

  if [ $# -gt 0 ] ; then
    # invalid
    if [ "x`echo $1 | grep -E '^/\.\.'`" != "x" ] ; then
      echo $1
      return -1
    fi

    # convert to absolute path
    if [ "x`echo $1 | grep -E '^\/'`" == "x" ] ; then
      normalize "`pwd`/$1"
      return $?
    fi

    ret=`echo $1 | sed 's;/\.\($\|/\);/;g' | sed 's;/[^/]*[^/.]\+[^/]*/\.\.\($\|/\);/;g'`
  else
    read line
    normalize "$line"
    return $?
  fi

  if [ "x`echo $ret | grep -E '/\.\.?(/|$)'`" != "x" ] ; then
    ret=`normalize "$ret"`
    rc=$?
  fi

  echo "$ret"
  return $rc
}

https://gist.github.com/bestofsong/8830bdf3e5eb9461d27313c3c282868c

bestOfSong
la source
0

J'ai créé une fonction intégrée pour gérer cela en mettant l'accent sur les performances les plus élevées possibles (pour le plaisir). Il ne résout pas les liens symboliques, c'est donc essentiellement le même que realpath -sm.

## A bash-only mimic of `realpath -sm`. 
## Give it path[s] as argument[s] and it will convert them to clean absolute paths
abspath () { 
  ${*+false} && { >&2 echo $FUNCNAME: missing operand; return 1; };
  local c s p IFS='/';  ## path chunk, absolute path, input path, IFS for splitting paths into chunks
  local -i r=0;         ## return value

  for p in "$@"; do
    case "$p" in        ## Check for leading backslashes, identify relative/absolute path
    '') ((r|=1)); continue;;
    //[!/]*)  >&2 echo "paths =~ ^//[^/]* are impl-defined; not my problem"; ((r|=2)); continue;;
    /*) ;;
    *)  p="$PWD/$p";;   ## Prepend the current directory to form an absolute path
    esac

    s='';
    for c in $p; do     ## Let IFS split the path at '/'s
      case $c in        ### NOTE: IFS is '/'; so no quotes needed here
      ''|.) ;;          ## Skip duplicate '/'s and '/./'s
      ..) s="${s%/*}";; ## Trim the previous addition to the absolute path string
      *)  s+=/$c;;      ### NOTE: No quotes here intentionally. They make no difference, it seems
      esac;
    done;

    echo "${s:-/}";     ## If xpg_echo is set, use `echo -E` or `printf $'%s\n'` instead
  done
  return $r;
}

Remarque: Cette fonction ne gère pas les chemins commençant par //, car exactement deux doubles barres obliques au début d'un chemin correspondent à un comportement défini par l'implémentation. Cependant, il gère /,/// et ainsi de suite.

Cette fonction semble gérer correctement tous les cas marginaux, mais il y en a peut-être encore que je n'ai pas traités.

Remarque sur les performances: lorsqu'il est appelé avec des milliers d'arguments, il abspathest environ 10 fois plus lent que realpath -sm; lorsqu'il est appelé avec un seul argument, abspaths'exécute> 110 fois plus vite que realpath -smsur ma machine, principalement parce qu'il n'est pas nécessaire d'exécuter un nouveau programme à chaque fois.

Jeffrey Cash
la source
-1

J'ai découvert aujourd'hui que vous pouvez utiliser la statcommande pour résoudre les chemins.

Donc pour un répertoire comme "~ / Documents":

Vous pouvez exécuter ceci:

stat -f %N ~/Documents

Pour obtenir le chemin complet:

/Users/me/Documents

Pour les liens symboliques, vous pouvez utiliser l'option de format% Y:

stat -f %Y example_symlink

Ce qui pourrait retourner un résultat comme:

/usr/local/sbin/example_symlink

Les options de formatage peuvent être différentes sur d'autres versions de * NIX, mais elles fonctionnent pour moi sur OSX.

Coldlogic
la source
1
la stat -f %N ~/Documentsligne est un hareng rouge ... votre shell remplace ~/Documentspar /Users/me/Documents, et stataffiche simplement son argument textuellement.
danwyand
-4

Une solution simple utilisant node.js:

#!/usr/bin/env node
process.stdout.write(require('path').resolve(process.argv[2]));
Artisan72
la source