web-dev-qa-db-fra.com

Chemin absolu du script Bash avec OS X

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 pour readlink -f $0. Cependant, puisque readlink d'OS X est identique à BSD, cela ne fonctionne tout simplement pas (cela fonctionne avec la version de GNU).

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

79

Il y a une fonction realpath() 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"

Ceci imprime le chemin textuellement s'il commence par un /. Sinon, ce doit être un chemin relatif, donc il précède $PWD à l'avant. Le #./ une partie se déshabille ./ de l'avant de $1.

72
John Kugelman

Ces trois étapes simples vont résoudre ce problème et bien d'autres problèmes OS X:

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

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

94
Oleg Mikheev

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". Bien que la question d'origine demande explicitement un "script Bash", elle mentionne également le type 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 cela devrait également fonctionner avec des espaces dans le (s) chemin (s), bien que je ne sois 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.

22
Geoff Nixon

Une variante plus conviviale de la solution Python:

python -c "import os; print(os.path.realpath('$1'))"
9
nanav yorbiz

Je cherchais une solution à utiliser dans un script de fourniture de système, c'est-à-dire à exécuter avant même l'installation de Homebrew. En l'absence d'une solution appropriée, je déchargerais simplement la tâche dans 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")
6
4ae1e1

Puisqu'il y a un realpath 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)

Compilez ensuite avec make et insérez un lien logiciel avec:
ln -s $(pwd)/realpath /usr/local/bin/realpath

6
WaffleSouffle

Utilisez Python pour l'obtenir:

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

print(os.path.realpath(sys.argv[1]))
6
acrazing

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. J'étais complètement choqué de voir à quel point c'était rudimentaire; Je m'apprends à coder assez intensivement depuis environ un an maintenant, mais j'ai souvent l'impression que je n'ai peut-être rien appris du tout lorsque les choses sont au pire.

Je supprimerais la "solution" ci-dessus, mais j'aime vraiment que ce soit un enregistrement de ce que j'ai vraiment appris au cours des derniers mois.

Mais je m'égare. Je me suis assis et j'ai tout travaillé hier soir. L'explication dans les commentaires devrait être suffisante. Si vous voulez suivre la copie sur laquelle je continue de travailler, vous pouvez suivre ce Gist. 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
2
Geoff Nixon

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 d'accueil

realpath "~/Test/../Test/scripts/test.sh"
2
Evgeny Karpov

Sur macOS, la seule solution que j'ai trouvée à cela qui gère de manière fiable les liens symboliques est d'utiliser realpath. Étant donné que cela nécessite brew install coreutils, Je viens d'automatiser cette étape. Mon implémentation 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\""


Cette erreur si brew n'est pas installé, mais vous pouvez également l'installer également. Je ne me sentais tout simplement pas à l'aise d'automatiser quelque chose qui ondule arbitrairement Ruby code du net.

Notez que c'est une variation automatisée de Oleg Mikheev réponse .


Un test important

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

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

La solution déréférencer le lien symbolique et vous donne le répertoire d'origine? Si c'est le cas, cela fonctionne.

0
Clay Bridges