web-dev-qa-db-fra.com

Moyen fiable pour un script Bash d'obtenir le chemin complet vers lui-même

J'ai un script Bash qui doit connaître son chemin complet. J'essaie de trouver un moyen largement compatible de faire cela sans me retrouver avec des chemins relatifs ou d'aspect funky. Je n'ai besoin que de soutenir Bash, pas sh, csh, etc.

Ce que j'ai trouvé jusqu'à présent:

  1. La réponse acceptée à Obtenir le répertoire source d'un script Bash de l'intérieur adresse le chemin d'accès au script via dirname $0, ce qui est correct, mais peut renvoyer un chemin relatif (comme .), qui pose un problème si vous souhaitez modifier les répertoires dans le script et que le chemin pointe toujours vers le répertoire du script. Néanmoins, dirname fera partie du puzzle.

  2. La réponse acceptée à chemin absolu du script Bash avec OS X(spécifique à OS X, mais la réponse fonctionne quand même) donne une fonction qui sera testée voyez si $0 a l'air relatif et si c'est le cas, il sera précédé de $PWD. Mais le résultat peut toujours contenir des bits relatifs (bien que globalement, il soit absolu) - par exemple, si le script est t dans le répertoire /usr/bin et que vous êtes dans /usr et que vous tapez bin/../bin/t pour l'exécuter (oui, c'est compliqué), vous vous retrouvez avec /usr/bin/../bin comme chemin du répertoire du script. Qui fonctionne , mais ...

  3. La solution readlinksur cette page , qui ressemble à ceci:

    # Absolute path to this script. /home/user/bin/foo.sh
    SCRIPT=$(readlink -f $0)
    # Absolute path this script is in. /home/user/bin
    SCRIPTPATH=`dirname $SCRIPT`
    

    Mais readlink n'est pas POSIX et apparemment, la solution repose sur GNU readlink où les BSD ne fonctionneront pas pour une raison quelconque (je n'ai pas accès à un système de type BSD à vérifier).

Donc, différentes façons de le faire, mais ils ont tous leurs mises en garde.

Quel serait un meilleur moyen? Où "mieux" signifie:

  • Me donne le chemin absolu.
  • Supprime les bits funky même lorsqu'il est invoqué de manière alambiquée (voir le commentaire sur le point 2 ci-dessus). (Par exemple, au moins modérément canonise le chemin.)
  • Ne repose que sur des Bach-ismes ou des éléments dont il est presque certain qu'ils se trouveront dans la plupart des versions courantes des systèmes * nix (GNU/Linux, BSD et des systèmes similaires à BSD tels que OS X, etc.).
  • Évite si possible d’appeler des programmes externes (par exemple, préfère les fonctions intégrées à Bash).
  • ( Mis à jour , merci pour le heads up, wich ) Il n'est pas nécessaire de résoudre les liens symboliques (en fait, je ' d préfèrent les laisser seuls, mais ce n’est pas une obligation).
622
T.J. Crowder

Voici ce que j'ai trouvé (modifier: plus quelques réglages fournis par sfstewman , levigroker , Kyle Strand , et Rob Kennedy ), cela semble correspondre principalement à mes "meilleurs" critères:

SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"

Cette ligne SCRIPTPATH semble particulièrement détournée, mais nous en avons besoin plutôt que SCRIPTPATH=`pwd` pour gérer correctement les espaces et les liens symboliques.

Notez également que les situations ésotériques, telles que l'exécution d'un script qui ne provient pas d'un fichier d'un système de fichiers accessible (ce qui est parfaitement possible), ne sont pas prises en compte ici (ni dans aucune des réponses précédentes ).

500
T.J. Crowder

Je suis surpris que la commande realpath n'ait pas été mentionnée ici. D'après ce que j'ai compris, il est largement portable/transférable.

Votre solution initiale devient:

SCRIPT=`realpath $0`
SCRIPTPATH=`dirname $SCRIPT`

Et pour laisser les liens symboliques non résolus selon vos préférences:

SCRIPT=`realpath -s $0`
SCRIPTPATH=`dirname $SCRIPT`
199

Le moyen le plus simple que j'ai trouvé pour obtenir un chemin complet dans Bash est d'utiliser cd et pwd:

ABSOLUTE_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"

L'utilisation de ${BASH_SOURCE[0]} au lieu de $0 produit le même comportement, que le script soit appelé en tant que <name> ou source <name>.

159
Andrew Norrie

Je viens juste de revoir ce problème aujourd'hui et j'ai trouvé Obtenez le répertoire source d'un script Bash dans le script lui-même:

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

Il y a plus de variantes dans la réponse liée, par exemple. pour le cas où le script lui-même est un lien symbolique.

46
Felix Rabe

Obtenir le chemin absolu d'un script shell

Il n'utilise pas l'option -f dans readlink et devrait donc fonctionner sous BSD/Mac OS X.

Les soutiens

  • source ./script (lorsqu'il est appelé par l'opérateur de point .)
  • Chemin absolu/chemin/vers/script
  • Chemin relatif comme ./script
  • /path/dir1/../dir2/dir3/../script
  • Appelé depuis un lien symbolique
  • Lorsque le lien symbolique est imbriqué, par exemple) foo->dir1/dir2/bar bar->./../doe doe->script
  • Lorsque l'appelant change le nom du script

Je cherche des cas où ce code ne fonctionne pas. S'il vous plaît, faites-moi savoir.

Code

pushd . > /dev/null
SCRIPT_PATH="${BASH_SOURCE[0]}";
while([ -h "${SCRIPT_PATH}" ]); do
    cd "`dirname "${SCRIPT_PATH}"`"
    SCRIPT_PATH="$(readlink "`basename "${SCRIPT_PATH}"`")";
done
cd "`dirname "${SCRIPT_PATH}"`" > /dev/null
SCRIPT_PATH="`pwd`";
popd  > /dev/null
echo "srcipt=[${SCRIPT_PATH}]"
echo "pwd   =[`pwd`]"

Problèmes connus

Le script doit être sur le disque quelque part . Que ce soit sur un réseau. Si vous essayez d'exécuter ce script à partir d'un PIPE, cela ne fonctionnera pas

wget -o /dev/null -O - http://Host.domain/dir/script.sh |bash

Techniquement parlant, c'est indéfini. Pratiquement, il n'y a pas de moyen sain de détecter cela. (Un co-processus ne peut pas accéder à l'environnement du parent.)

36
GreenFox

Utilisation:

SCRIPT_PATH=$(dirname `which $0`)

which affiche sur la sortie standard le chemin d'accès complet de l'exécutable qui aurait été exécuté lorsque l'argument transmis avait été entré à l'invite du shell (ce que contient $ 0)

dirname supprime le suffixe hors répertoire d'un nom de fichier.

Par conséquent, vous vous retrouvez avec le chemin complet du script, peu importe si le chemin a été spécifié ou non.

31
Matt

Comme realpath n'est pas installé par défaut sur mon système Linux, les opérations suivantes fonctionnent pour moi:

SCRIPT="$(readlink --canonicalize-existing "$0")"
SCRIPTPATH="$(dirname "$SCRIPT")"

$SCRIPT contiendra le chemin du fichier réel vers le script et $SCRIPTPATH le chemin réel du répertoire contenant le script.

Avant d’utiliser ceci, lisez les commentaires de cette réponse .

24
ypid

Facile à lire? Ci-dessous une alternative. Il ignore les liens symboliques

#!/bin/bash
currentDir=$(
  cd $(dirname "$0")
  pwd
)

echo -n "current "
pwd
echo script $currentDir

Depuis que j'ai posté la réponse ci-dessus il y a quelques années, j'ai évolué vers l'utilisation de ce paradigme spécifique à Linux, qui gère correctement les liens symboliques:

Origin=$(dirname $(readlink -f $0))
13
gerardw

Répondre à cette question très tard, mais j'utilise:

SCRIPT=$( readlink -m $( type -p $0 ))      # Full path to script
BASE_DIR=`dirname ${SCRIPT}`                # Directory script is run in
NAME=`basename ${SCRIPT}`                   # Actual name of script even if linked
10
Stormcloud

Nous avons placé notre propre produit realpath-lib sur GitHub pour une utilisation communautaire libre et non encombrée.

Prise sans vergogne mais avec cette bibliothèque Bash vous pouvez:

get_realpath <absolute|relative|symlink|local file>

Cette fonction est le noyau 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 ne nécessite aucune dépendance externe, juste Bash 4+. Contient également des fonctions pour get_dirname, get_filename, get_stemname et validate_path validate_realpath. C'est gratuit, propre, simple et bien documenté, il peut donc être utilisé à des fins d'apprentissage et peut sans aucun doute être amélioré. Essayez-le sur toutes les plateformes.

Mise à jour: après quelques tests et tests, nous avons remplacé la fonction ci-dessus par un résultat qui donne le même résultat (sans utiliser dirname, uniquement du pur Bash) mais avec une meilleure efficacité:

function get_realpath() {

    [[ ! -f "$1" ]] && return 1 # failure : file does not exist.
    [[ -n "$no_symlinks" ]] && local pwdp='pwd -P' || local pwdp='pwd' # do symlinks.
    echo "$( cd "$( echo "${1%/*}" )" 2>/dev/null; $pwdp )"/"${1##*/}" # echo result.
    return 0 # success

}

Cela inclut également un paramètre d’environnement no_symlinks permettant de résoudre les liens symboliques vers le système physique. Par défaut, il garde les liens symboliques intacts.

7
AsymLabs

Simplement:

BASEDIR=$(readlink -f $0 | xargs dirname)

Les opérateurs de fantaisie ne sont pas nécessaires.

7
poussma

Vous pouvez essayer de définir la variable suivante:

CWD="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"

Ou vous pouvez essayer la fonction suivante dans Bash:

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

Cette fonction prend un argument. Si l'argument a déjà un chemin absolu, imprimez-le tel quel, sinon indiquez $PWD variable + argument nom du fichier (sans le préfixe ./).

Apparenté, relié, connexe:

5
kenorb

Bourne Shell (sh) de manière conforme:

SCRIPT_HOME=`dirname $0 | while read a; do cd $a && pwd && break; done`
4
Haruo Kinoshita

Considérant ce problème encore: il existe une solution très populaire qui est référencée dans ce fil qui a son origine ici :

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

Je me suis éloigné de cette solution en raison de l'utilisation de dirname - elle peut présenter des difficultés multi-plateformes, en particulier si un script doit être verrouillé pour des raisons de sécurité. Mais comme alternative pure à Bash, que diriez-vous d'utiliser:

DIR="$( cd "$( echo "${BASH_SOURCE[0]%/*}" )" && pwd )"

Serait-ce une option?

3
AsymLabs

Si nous utilisons Bash, je pense que c'est la méthode la plus pratique car elle ne nécessite aucun appel à une commande externe:

THIS_PATH="${BASH_SOURCE[0]}";
THIS_DIR=$(dirname $THIS_PATH)
3
Nordlöw

La solution acceptée a l'inconvénient (pour moi) de ne pas être "source-capable":
si vous l'appelez d'un "source ../../yourScript", $0 serait "bash"!

La fonction suivante (pour bash> = 3.0) me donne le bon chemin, mais le script peut être appelé (directement ou via source, avec un chemin absolu ou relatif):
(par "chemin correct", je veux dire le chemin absolu complet du script appelé, même lorsqu'il est appelé depuis un autre chemin, directement ou avec "source")

#!/bin/bash
echo $0 executed

function bashscriptpath() {
  local _sp=$1
  local ascript="$0"
  local asp="$(dirname $0)"
  #echo "b1 asp '$asp', b1 ascript '$ascript'"
  if [[ "$asp" == "." && "$ascript" != "bash" && "$ascript" != "./.bashrc" ]] ; then asp="${BASH_SOURCE[0]%/*}"
  Elif [[ "$asp" == "." && "$ascript" == "./.bashrc" ]] ; then asp=$(pwd)
  else
    if [[ "$ascript" == "bash" ]] ; then
      ascript=${BASH_SOURCE[0]}
      asp="$(dirname $ascript)"
    fi  
    #echo "b2 asp '$asp', b2 ascript '$ascript'"
    if [[ "${ascript#/}" != "$ascript" ]]; then asp=$asp ;
    Elif [[ "${ascript#../}" != "$ascript" ]]; then
      asp=$(pwd)
      while [[ "${ascript#../}" != "$ascript" ]]; do
        asp=${asp%/*}
        ascript=${ascript#../}
      done
    Elif [[ "${ascript#*/}" != "$ascript" ]];  then
      if [[ "$asp" == "." ]] ; then asp=$(pwd) ; else asp="$(pwd)/${asp}"; fi
    fi  
  fi  
  eval $_sp="'$asp'"
}

bashscriptpath H
export H=${H}

La clé consiste à détecter la casse "source" et à utiliser ${BASH_SOURCE[0]} pour récupérer le script réel.

2
VonC

Peut-être que la réponse acceptée à la question suivante peut être utile.

Comment puis-je obtenir le comportement de readlink -f de GNU sur un Mac?

Étant donné que vous voulez juste canoniser le nom que vous obtenez en concaténant $ PWD et $ 0 (en supposant que $ 0 ne soit pas absolu pour commencer), utilisez simplement une série de remplacements de regex le long de la ligne abs_dir=${abs_dir//\/.\//\/}, etc.

Oui, je sais que ça a l'air horrible, mais ça va marcher et c'est pur Bash.

2
wich

Essaye ça:

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

Juste pour le plaisir, j'ai fait un peu de piratage d'un script qui fait les choses purement textuel, uniquement en Bash. J'espère avoir attrapé tous les cas Edge.

Notez que le ${var//pat/repl} que j'ai mentionné dans l'autre réponse ne fonctionne pas car vous ne pouvez pas le faire remplacer uniquement la correspondance la plus courte possible, ce qui pose un problème pour remplacer /foo/../ comme par exemple. /*/../ prendra tout avant lui, pas seulement une entrée. Et comme ces motifs ne sont pas vraiment des expressions rationnelles, je ne vois pas comment cela pourrait fonctionner. Alors, voici la solution joliment compliquée que j'ai proposée, profitez-en. ;)

En passant, faites-moi savoir si vous trouvez des cas Edge non gérés.

#!/bin/bash

canonicalize_path() {
  local path="$1"
  OIFS="$IFS"
  IFS=$'/'
  read -a parts < <(echo "$path")
  IFS="$OIFS"

  local i=${#parts[@]}
  local j=0
  local back=0
  local -a rev_Canon
  while (($i > 0)); do
    ((i--))
    case "${parts[$i]}" in
      ""|.) ;;
      ..) ((back++));;
      *) if (($back > 0)); then
           ((back--))
         else
           rev_Canon[j]="${parts[$i]}"
           ((j++))
         fi;;
    esac
  done
  while (($j > 0)); do
    ((j--))
    echo -n "/${rev_Canon[$j]}"
  done
  echo
}

canonicalize_path "/.././..////../foo/./bar//foo/bar/.././bar/../foo/bar/./../..//../foo///bar/"
0
wich

Bon mot

`dirname $(realpath $0)`
0
Abhijit

J'ai utilisé l'approche suivante avec succès pendant un certain temps (pas sous OS X cependant), et elle utilise uniquement un shell intégré et gère le cas 'source foobar.sh' dans la mesure où je l'ai vue.

Un problème avec l'exemple de code ci-dessous (préparé hâtivement) est que la fonction utilise $ PWD, qui peut être incorrecte ou non au moment de l'appel de la fonction. Donc, cela doit être traité.

#!/bin/bash

function canonical_path() {
  # Handle relative vs absolute path
  [ ${1:0:1} == '/' ] && x=$1 || x=$PWD/$1
  # Change to dirname of x
  cd ${x%/*}
  # Combine new pwd with basename of x
  echo $(pwd -P)/${x##*/}
  cd $OLDPWD
}

echo $(canonical_path "${BASH_SOURCE[0]}")

type [
type cd
type echo
type pwd
0
andro