Chemin absolu du script bash avec OS X

98

J'essaie d'obtenir le chemin absolu vers le script en cours d'exécution sur OS X.

J'ai vu de nombreuses réponses readlink -f $0. Cependant, puisque OS X readlinkest le même que BSD, cela ne fonctionne tout simplement pas (cela fonctionne avec la version de GNU).

Y a-t-il une solution prête à l'emploi à cela?

Le puissant canard en caoutchouc
la source

Réponses:

88

Il y a une realpath()fonction C qui fera le travail, mais je ne vois rien de disponible sur la ligne de commande. Voici un remplacement rapide et sale:

#!/bin/bash

realpath() {
    [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
}

realpath "$0"

Cela imprime le chemin textuellement s'il commence par un /. Sinon, il doit s'agir d'un chemin relatif, il est donc précédé $PWDdu début . La #./pièce se détache ./de l'avant $1.

John Kugelman
la source
1
J'ai également remarqué la fonction C mais je n'ai pas trouvé de binaire correspondant ou quoi que ce soit. Quoi qu'il en soit, votre fonction correspond parfaitement à mes besoins. Merci!
The Mighty Rubber Duck
7
Notez que cela ne déréférence pas les liens symboliques.
Adam Vandenberg
17
realpath ../somethingrenvoie$PWD/../something
Lri
Cela a fonctionné pour moi, mais je l'ai renommé "realpath_osx ()" car je POURrais avoir besoin d'envoyer ce script bash à un shell Linux et je ne voulais pas qu'il entre en collision avec le "vrai" realpath! (Je suis sûr qu'il y a un moyen plus élégant, mais je suis un bash n00b.)
Andrew Theken
8
command -v realpath >/dev/null 2>&1 || realpath() { ... }
Kara Brightwell
115

Ces trois étapes simples vont résoudre ce problème et de nombreux autres problèmes OS X:

  1. Installez Homebrew
  2. brew install coreutils
  3. grealpath .

(3) peut être changé en juste realpath, voir (2) sortie

Oleg Mikheev
la source
3
Cela résout le problème, mais nécessite d'installer des éléments dont vous pourriez ne pas vouloir ou dont vous n'avez peut-être pas besoin, ou qui peuvent ne pas être sur le système cible, c'est-à-dire OS X
Jason S
6
@JasonS GNU Coreutils est trop beau pour ne pas être utilisé. Il comprend de nombreux excellents utilitaires. C'est tellement bien en fait que le noyau Linux est inutile sans lui et pourquoi certains l'appellent GNU / Linux. Coreutils est génial. excellente réponse cela devrait être la réponse choisie.
7
@omouse La question mentionne spécifiquement «une solution prête à l'emploi» (pour OS X). Peu importe à quel point coreutils est génial, il n'est pas `` prêt à l'emploi '' sur OS X, donc la réponse n'est pas bonne. Ce n'est pas une question de savoir à quel point coreutils est bon, ou s'il est meilleur que ce qui est sur OS X. Il existe des solutions «prêtes à l'emploi» qui $( cd "$(dirname "$0")" ; pwd -P )fonctionnent bien pour moi.
Jason S
2
L'une des "caractéristiques" d'OS X est que le répertoire et les noms de fichiers sont insensibles à la casse. Par conséquent , si vous avez un répertoire appelé XXXet quelqu'un cd xxxpuis pwdretournera .../xxx. À l'exception de cette réponse, toutes les solutions ci-dessus reviennent xxxlorsque ce que vous voulez vraiment est XXX. Je vous remercie!
Andrew
1
Je ne sais pas comment copier, coller et réinventer du code à l'infini est mieux qu'une solution de gestionnaire de packages existante. Si vous en aviez besoin realpath, que se passe-t-il lorsque vous aurez presque certainement besoin d'autres articles coreutils? Réécrire ces fonctions dans bash aussi? : P
Ezekiel Victor
28

Pouah. J'ai trouvé les réponses précédentes un peu insuffisantes pour plusieurs raisons: en particulier, elles ne résolvent pas plusieurs niveaux de liens symboliques, et elles sont extrêmement "Bash-y". Alors que la question originale demande explicitement un "script Bash", elle fait également mention du BSD de Mac OS X, non GNU readlink. Voici donc une tentative de portabilité raisonnable (je l'ai vérifié avec bash comme «sh» et dash), résolvant un nombre arbitraire de liens symboliques; et il devrait également fonctionner avec des espaces dans le (s) chemin (s), même si je ne suis pas sûr du comportement s'il y a un espace blanc le nom de base de l'utilitaire lui-même, alors peut-être, euh, éviter cela?

#!/bin/sh
realpath() {
  OURPWD=$PWD
  cd "$(dirname "$1")"
  LINK=$(readlink "$(basename "$1")")
  while [ "$LINK" ]; do
    cd "$(dirname "$LINK")"
    LINK=$(readlink "$(basename "$1")")
  done
  REALPATH="$PWD/$(basename "$1")"
  cd "$OURPWD"
  echo "$REALPATH"
}
realpath "$@"

J'espère que cela peut être utile à quelqu'un.

Geoff Nixon
la source
1
Je recommanderais uniquement d'utiliser localpour les variables définies dans la fonction pour ne pas polluer l'espace de noms global. Par exemple local OURPWD=.... Fonctionne au moins pour bash.
Michael Paesold
De plus, le code ne doit pas utiliser de majuscules pour les variables privées. Les variables majuscules sont réservées à l'utilisation du système.
tripleee
Merci pour le script. Dans le cas où le (s) lien (s) et le fichier réel ont des noms de base différents, ce serait probablement une bonne idée d'ajouter un BASENAME=$(basename "$LINK") à l'intérieur du while et de l'utiliser dans le deuxième setter LINK et le setter
REALPATH
Cela ne gère pas les liens symboliques et ..les références parent comme le realpathferait. Avec homebrew coreutilsinstallé, essayez ln -s /var/log /tmp/linkexamplealors realpath /tmp/linkexample/../; cela imprime /private/var. Mais votre fonction produit à la /tmp/linkexample/..place, car ce ..n'est pas un lien symbolique.
Martijn Pieters
10

Une variante plus conviviale de la solution Python en ligne de commande:

python -c "import os; print(os.path.realpath('$1'))"
nanav yorbiz
la source
2
Juste au cas où quelqu'un serait assez fou pour démarrer un interpréteur python pour une seule commande…
Bachsau
J'étais assez fou, mais utilisépython -c "import os; import sys; print(os.path.realpath(sys.argv[1]))"
Alex Chamberlain
7

Je cherchais une solution à utiliser dans un script de provision de système, c'est-à-dire à exécuter avant même que Homebrew ne soit installé. En l'absence de solution appropriée, je déchargerais simplement la tâche vers un langage multiplateforme, par exemple Perl:

script_abspath=$(perl -e 'use Cwd "abs_path"; print abs_path(@ARGV[0])' -- "$0")

Le plus souvent, ce que nous voulons réellement, c'est le répertoire contenant:

here=$(perl -e 'use File::Basename; use Cwd "abs_path"; print dirname(abs_path(@ARGV[0]));' -- "$0")
4ae1e1
la source
Génial! Perl est bien pour les petits frais généraux! Vous pourriez probablement simplifier la première version en FULLPATH=$(perl -e "use Cwd 'abs_path'; print abs_path('$0')"). Une raison contre?
F Pereira
@FPereira La génération de code de programme à partir d'une chaîne fournie par l'utilisateur sans échappement n'est jamais une bonne idée. ''n'est pas à l'épreuve des balles. Il casse s'il $0contient un guillemet simple, par exemple. Un exemple très simple: essayez votre version dans /tmp/'/test.sh, et appelez /tmp/'/test.shpar son chemin complet.
4ae1e1
Ou encore plus simple, /tmp/'.sh.
4ae1e1
6

Puisqu'il existe un chemin réel comme d'autres l'ont souligné:

// realpath.c
#include <stdio.h>
#include <stdlib.h>

int main (int argc, char* argv[])
{
  if (argc > 1) {
    for (int argIter = 1; argIter < argc; ++argIter) {
      char *resolved_path_buffer = NULL;
      char *result = realpath(argv[argIter], resolved_path_buffer);

      puts(result);

      if (result != NULL) {
        free(result);
      }
    }
  }

  return 0;
}

Makefile:

#Makefile
OBJ = realpath.o

%.o: %.c
      $(CC) -c -o $@ $< $(CFLAGS)

realpath: $(OBJ)
      gcc -o $@ $^ $(CFLAGS)

Ensuite, compilez avec makeet mettez un lien souple avec:
ln -s $(pwd)/realpath /usr/local/bin/realpath

GaufreSouffle
la source
pourrions-nous juste faire gcc realpath.c -o /usr/local/bin/realpath?
Alexander Mills
1
@AlexanderMills Vous ne devez pas exécuter le compilateur en tant que root; et si vous ne le faites pas, vous n'aurez pas les privilèges pour écrire/usr/local/bin
tripleee
6

Utilisez Python pour l'obtenir:

#!/usr/bin/env python
import os
import sys

print(os.path.realpath(sys.argv[1]))
pâturage
la source
2
abs_path () {    
   echo "$(cd $(dirname "$1");pwd)/$(basename "$1")"
}

dirnamedonnera le nom du répertoire de /path/to/file, ie /path/to.

cd /path/to; pwd garantit que le chemin est absolu.

basenamedonnera juste le nom de fichier dans /path/to/file, ie file.

Logu
la source
Bien que ce code puisse répondre à la question, fournir un contexte supplémentaire concernant la raison et / ou la manière dont ce code répond à la question améliore sa valeur à long terme.
Igor F.
1

Donc, comme vous pouvez le voir ci-dessus, j'ai pris une photo il y a environ 6 mois. Je l'ai totalement oublié jusqu'à ce que je me retrouve à nouveau dans le besoin d'une chose similaire. J'ai été complètement choqué de voir à quel point c'était rudimentaire; Je m'apprends à coder de manière assez intensive depuis environ un an maintenant, mais j'ai souvent l'impression que je n'ai peut-être rien appris du tout quand les choses vont au pire.

Je supprimerais la «solution» ci-dessus, mais j'aime vraiment qu'elle soit en quelque sorte un enregistrement de tout ce que j'ai vraiment appris au cours des derniers mois.

Mais je m'éloigne du sujet. Je me suis assis et j'ai tout travaillé hier soir. L'explication dans les commentaires devrait être suffisante. Si vous souhaitez suivre la copie sur laquelle je continue de travailler, vous pouvez suivre l'essentiel. Cela fait probablement ce dont vous avez besoin.

#!/bin/sh # dash bash ksh # !zsh (issues). G. Nixon, 12/2013. Public domain.

## 'linkread' or 'fullpath' or (you choose) is a little tool to recursively
## dereference symbolic links (ala 'readlink') until the originating file
## is found. This is effectively the same function provided in stdlib.h as
## 'realpath' and on the command line in GNU 'readlink -f'.

## Neither of these tools, however, are particularly accessible on the many
## systems that do not have the GNU implementation of readlink, nor ship
## with a system compiler (not to mention the requisite knowledge of C).

## This script is written with portability and (to the extent possible, speed)
## in mind, hence the use of printf for echo and case statements where they
## can be substituded for test, though I've had to scale back a bit on that.

## It is (to the best of my knowledge) written in standard POSIX shell, and
## has been tested with bash-as-bin-sh, dash, and ksh93. zsh seems to have
## issues with it, though I'm not sure why; so probably best to avoid for now.

## Particularly useful (in fact, the reason I wrote this) is the fact that
## it can be used within a shell script to find the path of the script itself.
## (I am sure the shell knows this already; but most likely for the sake of
## security it is not made readily available. The implementation of "$0"
## specificies that the $0 must be the location of **last** symbolic link in
## a chain, or wherever it resides in the path.) This can be used for some
## ...interesting things, like self-duplicating and self-modifiying scripts.

## Currently supported are three errors: whether the file specified exists
## (ala ENOENT), whether its target exists/is accessible; and the special
## case of when a sybolic link references itself "foo -> foo": a common error
## for beginners, since 'ln' does not produce an error if the order of link
## and target are reversed on the command line. (See POSIX signal ELOOP.)

## It would probably be rather simple to write to use this as a basis for
## a pure shell implementation of the 'symlinks' util included with Linux.

## As an aside, the amount of code below **completely** belies the amount
## effort it took to get this right -- but I guess that's coding for you.

##===-------------------------------------------------------------------===##

for argv; do :; done # Last parameter on command line, for options parsing.

## Error messages. Use functions so that we can sub in when the error occurs.

recurses(){ printf "Self-referential:\n\t$argv ->\n\t$argv\n" ;}
dangling(){ printf "Broken symlink:\n\t$argv ->\n\t"$(readlink "$argv")"\n" ;}
errnoent(){ printf "No such file: "$@"\n" ;} # Borrow a horrible signal name.

# Probably best not to install as 'pathfull', if you can avoid it.

pathfull(){ cd "$(dirname "$@")"; link="$(readlink "$(basename "$@")")"

## 'test and 'ls' report different status for bad symlinks, so we use this.

 if [ ! -e "$@" ]; then if $(ls -d "$@" 2>/dev/null) 2>/dev/null;  then
    errnoent 1>&2; exit 1; elif [ ! -e "$@" -a "$link" = "$@" ];   then
    recurses 1>&2; exit 1; elif [ ! -e "$@" ] && [ ! -z "$link" ]; then
    dangling 1>&2; exit 1; fi
 fi

## Not a link, but there might be one in the path, so 'cd' and 'pwd'.

 if [ -z "$link" ]; then if [ "$(dirname "$@" | cut -c1)" = '/' ]; then
   printf "$@\n"; exit 0; else printf "$(pwd)/$(basename "$@")\n"; fi; exit 0
 fi

## Walk the symlinks back to the origin. Calls itself recursivly as needed.

 while [ "$link" ]; do
   cd "$(dirname "$link")"; newlink="$(readlink "$(basename "$link")")"
   case "$newlink" in
    "$link") dangling 1>&2 && exit 1                                       ;;
         '') printf "$(pwd)/$(basename "$link")\n"; exit 0                 ;;
          *) link="$newlink" && pathfull "$link"                           ;;
   esac
 done
 printf "$(pwd)/$(basename "$newlink")\n"
}

## Demo. Install somewhere deep in the filesystem, then symlink somewhere 
## else, symlink again (maybe with a different name) elsewhere, and link
## back into the directory you started in (or something.) The absolute path
## of the script will always be reported in the usage, along with "$0".

if [ -z "$argv" ]; then scriptname="$(pathfull "$0")"

# Yay ANSI l33t codes! Fancy.
 printf "\n\033[3mfrom/as: \033[4m$0\033[0m\n\n\033[1mUSAGE:\033[0m   "
 printf "\033[4m$scriptname\033[24m [ link | file | dir ]\n\n         "
 printf "Recursive readlink for the authoritative file, symlink after "
 printf "symlink.\n\n\n         \033[4m$scriptname\033[24m\n\n        "
 printf " From within an invocation of a script, locate the script's "
 printf "own file\n         (no matter where it has been linked or "
 printf "from where it is being called).\n\n"

else pathfull "$@"
fi
Geoff Nixon
la source
1
Le lien essentiel semble rompu.
Alex
cette réponse fonctionne: stackoverflow.com/a/46772980/1223975 .... vous devriez simplement l'utiliser.
Alexander Mills
Notez que votre implémentation ne résout pas les liens symboliques avant ..les références parentes; par exemple, /foo/link_to_other_directory/..est résolu comme /fooplutôt que comme parent du chemin vers lequel /foo/link_to_other_directorypointe le lien symbolique . readlink -fet realpathrésoudre chaque composant de chemin en commençant à la racine et en mettant à jour les cibles de lien en tête vers le reste en cours de traitement. J'ai ajouté une réponse à cette question pour réimplémenter cette logique.
Martijn Pieters
1

realpath pour Mac OS X

realpath() {
    path=`eval echo "$1"`
    folder=$(dirname "$path")
    echo $(cd "$folder"; pwd)/$(basename "$path"); 
}

Exemple avec chemin associé:

realpath "../scripts/test.sh"

Exemple avec dossier personnel

realpath "~/Test/../Test/scripts/test.sh"
Evgeny Karpov
la source
1
belle solution simple, je viens de trouver une mise en garde, lorsque l'invocation avec ..elle ne produit pas la bonne réponse, j'ai donc ajouté un contrôle si le chemin donné est un répertoire: if test -d $path ; then echo $(cd "$path"; pwd) ; else [...]
herbert
Cela n'a pas fonctionné pour moi, alors que "$(dirname $(dirname $(realpath $0)))"ça marche, alors besoin de quelque chose d'autre ...
Alexander Mills
L' usage inutile deecho ne doit pas non plus être perpétré.
tripleee
Cela ne résout pas vraiment les liens symboliques comme le ferait realpath. Il résout ..les références parentes avant de résoudre les liens symboliques, pas après; essayez ceci avec homebrew coreutilsinstallé, créez un lien avec ln -s /var/log /tmp/linkexamplepuis exécutez realpath /tmp/linkexample/../; cela imprime /private/var. Mais votre fonction produit à la /tmp/linkexample/..place, car pwdapparaît toujours /tmp/linkexampleaprès le cd.
Martijn Pieters
1

Sur macOS, la seule solution que j'ai trouvée pour gérer les liens symboliques de manière fiable consiste à utiliser realpath. Puisque cela l'exige brew install coreutils, je viens d'automatiser cette étape. Ma mise en œuvre ressemble à ceci:

#!/usr/bin/env bash

set -e

if ! which realpath >&/dev/null; then
  if ! which brew >&/dev/null; then
    msg="ERROR: This script requires brew. See https://brew.sh for installation instructions."
    echo "$(tput setaf 1)$msg$(tput sgr0)" >&2
    exit 1
  fi
  echo "Installing coreutils/realpath"
  brew install coreutils >&/dev/null
fi

thisDir=$( dirname "`realpath "$0"`" )
echo "This script is run from \"$thisDir\""


Ces erreurs s'ils ne sont pas brewinstallés, mais vous pouvez également l'installer également. Je ne me sentais tout simplement pas à l'aise d'automatiser quelque chose qui boucle du code ruby ​​arbitraire à partir du net.

Notez qu'il s'agit d'une variante automatisée de la réponse d' Oleg Mikheev .


Un test important

Un bon test de l'une de ces solutions est:

  1. mettre le code dans un fichier de script quelque part
  2. dans un autre répertoire, symlink ( ln -s) vers ce fichier
  3. exécuter le script à partir de ce lien symbolique

La solution déréférencera-t-elle le lien symbolique et vous donnera-t-elle le répertoire d'origine? Si c'est le cas, cela fonctionne.

Ponts d'argile
la source
0

Cela semble fonctionner pour OSX, ne nécessite aucun binaire et a été extrait d'ici

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

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

J'aime ça:

#!/usr/bin/env bash
function realpath() {
    local _X="$PWD"
    local _LNK=$1
    cd "$(dirname "$_LNK")"
    if [ -h "$_LNK" ]; then
        _LNK="$(readlink "$_LNK")"
        cd "$(dirname "$_LNK")"
    fi
    echo "$PWD/$(basename "$_LNK")"
    cd "$_X"
}
dcmorse
la source
0

J'avais besoin d'un realpathremplacement sur OS X, un qui fonctionne correctement sur les chemins avec des liens symboliques et des références parentales comme le readlink -fferait . Cela inclut la résolution des liens symboliques dans le chemin avant de résoudre les références parent; Par exemple, si vous avez installé la coreutilsbouteille homebrew , exécutez:

$ ln -s /var/log/cups /tmp/linkeddir  # symlink to another directory
$ greadlink -f /tmp/linkeddir/..      # canonical path of the link parent
/private/var/log

Notez que cela readlink -fa été résolu /tmp/linkeddir avant de résoudre la ..référence de répertoire parent. Bien sûr, il n'y a readlink -fsur Mac soit .

Donc, dans le cadre de l'implémentation a bash pour realpathj'ai réimplémenté ce qu'un appel de canonicalize_filename_mode(path, CAN_ALL_BUT_LAST)fonction GNUlib fait , dans Bash 3.2; c'est aussi l'appel de fonction que GNU readlink -ffait:

# shellcheck shell=bash
set -euo pipefail

_contains() {
    # return true if first argument is present in the other arguments
    local elem value

    value="$1"
    shift

    for elem in "$@"; do 
        if [[ $elem == "$value" ]]; then
            return 0
        fi
    done
    return 1
}

_canonicalize_filename_mode() {
    # resolve any symlink targets, GNU readlink -f style
    # where every path component except the last should exist and is
    # resolved if it is a symlink. This is essentially a re-implementation
    # of canonicalize_filename_mode(path, CAN_ALL_BUT_LAST).
    # takes the path to canonicalize as first argument

    local path result component seen
    seen=()
    path="$1"
    result="/"
    if [[ $path != /* ]]; then  # add in current working dir if relative
        result="$PWD"
    fi
    while [[ -n $path ]]; do
        component="${path%%/*}"
        case "$component" in
            '') # empty because it started with /
                path="${path:1}" ;;
            .)  # ./ current directory, do nothing
                path="${path:1}" ;;
            ..) # ../ parent directory
                if [[ $result != "/" ]]; then  # not at the root?
                    result="${result%/*}"      # then remove one element from the path
                fi
                path="${path:2}" ;;
            *)
                # add this component to the result, remove from path
                if [[ $result != */ ]]; then
                    result="$result/"
                fi
                result="$result$component"
                path="${path:${#component}}"
                # element must exist, unless this is the final component
                if [[ $path =~ [^/] && ! -e $result ]]; then
                    echo "$1: No such file or directory" >&2
                    return 1
                fi
                # if the result is a link, prefix it to the path, to continue resolving
                if [[ -L $result ]]; then
                    if _contains "$result" "${seen[@]+"${seen[@]}"}"; then
                        # we've seen this link before, abort
                        echo "$1: Too many levels of symbolic links" >&2
                        return 1
                    fi
                    seen+=("$result")
                    path="$(readlink "$result")$path"
                    if [[ $path = /* ]]; then
                        # if the link is absolute, restart the result from /
                        result="/"
                    elif [[ $result != "/" ]]; then
                        # otherwise remove the basename of the link from the result
                        result="${result%/*}"
                    fi
                elif [[ $path =~ [^/] && ! -d $result ]]; then
                    # otherwise all but the last element must be a dir
                    echo "$1: Not a directory" >&2
                    return 1
                fi
                ;;
        esac
    done
    echo "$result"
}

Il inclut la détection de lien symbolique circulaire, sortant si le même chemin (intermédiaire) est vu deux fois.

Si tout ce dont vous avez besoin est readlink -f, vous pouvez utiliser ce qui précède comme:

readlink() {
    if [[ $1 != -f ]]; then  # poor-man's option parsing
        # delegate to the standard readlink command
        command readlink "$@"
        return
    fi

    local path result seenerr
    shift
    seenerr=
    for path in "$@"; do
        # by default readlink suppresses error messages
        if ! result=$(_canonicalize_filename_mode "$path" 2>/dev/null); then
            seenerr=1
            continue
        fi
        echo "$result"
    done
    if [[ $seenerr ]]; then
        return 1;
    fi
}

Pour realpath, j'ai aussi besoin --relative-toet le --relative-basesoutien, ce qui vous donne des chemins relatifs après canonisant:

_realpath() {
    # GNU realpath replacement for bash 3.2 (OS X)
    # accepts --relative-to= and --relative-base options
    # and produces canonical (relative or absolute) paths for each
    # argument on stdout, errors on stderr, and returns 0 on success
    # and 1 if at least 1 path triggered an error.

    local relative_to relative_base seenerr path

    relative_to=
    relative_base=
    seenerr=

    while [[ $# -gt 0 ]]; do
        case $1 in
            "--relative-to="*)
                relative_to=$(_canonicalize_filename_mode "${1#*=}")
                shift 1;;
            "--relative-base="*)
                relative_base=$(_canonicalize_filename_mode "${1#*=}")
                shift 1;;
            *)
                break;;
        esac
    done

    if [[
        -n $relative_to
        && -n $relative_base
        && ${relative_to#${relative_base}/} == "$relative_to"
    ]]; then
        # relative_to is not a subdir of relative_base -> ignore both
        relative_to=
        relative_base=
    elif [[ -z $relative_to && -n $relative_base ]]; then
        # if relative_to has not been set but relative_base has, then
        # set relative_to from relative_base, simplifies logic later on
        relative_to="$relative_base"
    fi

    for path in "$@"; do
        if ! real=$(_canonicalize_filename_mode "$path"); then
            seenerr=1
            continue
        fi

        # make path relative if so required
        if [[
            -n $relative_to
            && ( # path must not be outside relative_base to be made relative
                -z $relative_base || ${real#${relative_base}/} != "$real"
            )
        ]]; then
            local common_part parentrefs

            common_part="$relative_to"
            parentrefs=
            while [[ ${real#${common_part}/} == "$real" ]]; do
                common_part="$(dirname "$common_part")"
                parentrefs="..${parentrefs:+/$parentrefs}"
            done

            if [[ $common_part != "/" ]]; then
                real="${parentrefs:+${parentrefs}/}${real#${common_part}/}"
            fi
        fi

        echo "$real"
    done
    if [[ $seenerr ]]; then
        return 1
    fi
}

if ! command -v realpath > /dev/null 2>&1; then
    # realpath is not available on OSX unless you install the `coreutils` brew
    realpath() { _realpath "$@"; }
fi

J'ai inclus des tests unitaires dans ma demande de révision de code pour ce code .

Martijn Pieters
la source
-2

Sur la base de la communication avec le commentateur, j'ai convenu qu'il était très difficile et qu'il n'y avait aucun moyen trival d'implémenter un realpath se comporte totalement comme Ubuntu.

Mais la version suivante peut gérer les cas d'angle, la meilleure réponse ne peut pas et satisfaire mes besoins quotidiens sur macbook. Mettez ce code dans votre ~ / .bashrc et rappelez-vous:

  • arg ne peut être qu'un fichier ou un répertoire, pas de caractère générique
  • pas d'espaces dans le répertoire ou le nom de fichier
  • au moins le répertoire parent du fichier ou du répertoire existe
  • n'hésitez pas à utiliser. .. / chose, ils sont sûrs

    # 1. if is a dir, try cd and pwd
    # 2. if is a file, try cd its parent and concat dir+file
    realpath() {
     [ "$1" = "" ] && return 1

     dir=`dirname "$1"`
     file=`basename "$1"`

     last=`pwd`

     [ -d "$dir" ] && cd $dir || return 1
     if [ -d "$file" ];
     then
       # case 1
       cd $file && pwd || return 1
     else
       # case 2
       echo `pwd`/$file | sed 's/\/\//\//g'
     fi

     cd $last
    }
occia
la source
Vous voulez éviter l' utilisation inutile deecho . Fait pwdla même chose que echo $(pwd)sans avoir à générer une deuxième copie du shell. De plus, ne pas citer l'argument echoest un bogue (vous perdrez tous les espaces de début ou de fin, tous les espaces blancs internes adjacents, et les caractères génériques sont développés, etc.). Voir plus sur stackoverflow.com/questions/10067266/…
tripleee
De plus, le comportement des chemins inexistants est bogué; mais je suppose que c'est peut-être ce que la phrase «mais souvenez-vous» essaie de dire. Bien que le comportement sur Ubuntu ne soit certainement pas d'imprimer le répertoire actuel lorsque vous demandez le realpathrépertoire d'un répertoire inexistant.
tripleee
Par dir=$(dirname "$1"); file=$(basename "$1")souci de cohérence, préférez probablement à la syntaxe de backtick obsolète depuis longtemps. Notez également la citation correcte des arguments, encore une fois.
tripleee
Votre réponse mise à jour semble ne pas résoudre de nombreux bogues et en ajoute de nouveaux.
tripleee
Veuillez me donner quelques cas d'échec spécifiques, car tous les tests que je fais dans le bureau ubuntu 18.04 sont ok, merci.
occia