Une façon fiable pour un script bash d'obtenir le chemin complet vers lui-même? [dupliquer]

cette question a déjà une réponse ici:

  • obtenir le répertoire source D'un script Bash de l'intérieur 51 réponses

j'ai un script bash qui a besoin de connaître son chemin complet. J'essaie de trouver une façon largement compatible de faire ça sans finir avec relative ou funky chemins. J'ai seulement besoin de soutien bash, pas de sh, csh, etc.

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

  1. Les accepté de répondre à " le répertoire source d'un script Bash de l'intérieur " les adresses obtenir le chemin d'accès du script via dirname "151910920" , ce qui est bien, mais que peut retourner un relative chemin "(comme . ), qui est un problème si vous voulez modifier les répertoires dans le script et le chemin reste du script d'annuaire. Pourtant, dirname fera partie du puzzle.

  2. Les accepté de répondre à " script Bash chemin absolu avec OSX " (OS X, mais la réponse fonctionne indépendamment) donne une fonction qui permettra de tester pour voir si "151940920" semble relative, et si tel est pré-pend $PWD . Mais le résultat peut 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 alambiqué), vous finissez avec /usr/bin/../bin comme chemin de répertoire du script. Qui fonctionne , mais...

  3. la readlink solution sur cette page , qui ressemble à ceci:

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

    mais readlink n'est pas POSIX et apparemment la solution s'appuie sur GNU readlink où BSD ne fonctionnera 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 caveats.

quelle meilleure façon? Où "Meilleur" signifie:

  • me donne le chemin absolu.
  • élimine les bits funky même lorsqu'ils sont invoqués d'une manière alambiquée (voir le commentaire sur le N ° 2 ci-dessus). (Par exemple, au moins modérément canonique le chemin.)
  • S'appuie uniquement sur des bash-ismes ou des choses qui sont presque certains d'être sur les saveurs les plus populaires des systèmes *nix (GNU/Linux, BSD et des systèmes similaires à BSD comme OS X, etc.).
  • évite d'appeler des programmes externes si possible (par exemple, préfère bash built-ins).
  • ( mise à Jour , merci pour le heads up, qui ) N'a pas à résoudre les liens symboliques (en fait, je ferais en sorte de le préfèrent les a laissés seuls, mais ce n'est pas une obligation).
526
demandé sur Community 2011-01-23 16:24:16

23 réponses

voici ce que j'ai trouvé (edit: plus quelques tweaks fournis par sfstewman , levigroker , Kyle Strand , et Rob Kennedy ), qui semble correspondre principalement à mes" meilleurs "critères:

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

cette ligne SCRIPTPATH semble particulièrement rond-point, mais nous en avons besoin plutôt que SCRIPTPATH=`pwd` afin de gérer correctement les espaces et les liens symboliques.

Notez aussi que les situations ésotériques, telles que l'exécution d'un script qui ne vient pas du tout d'un fichier dans un système de fichiers accessible (ce qui est parfaitement possible), n'y sont pas traitées (ou dans aucune des autres réponses que j'ai vues).

417
répondu T.J. Crowder 2017-11-07 06:40:00

je suis surpris que la commande realpath n'ait pas été mentionnée ici. Je crois comprendre qu'il est largement portable / porté.

votre solution initiale devient:

SCRIPT=`realpath "151900920"`
SCRIPTPATH=`dirname $SCRIPT`

et de laisser les liens symboliques non résolus selon votre préférence:

SCRIPT=`realpath -s "151910920"`
SCRIPTPATH=`dirname $SCRIPT`
160
répondu Darshan Rivka Whittle 2012-06-20 07:11:49

le moyen le plus simple que j'ai trouvé pour obtenir un chemin canonique complet à bash est d'utiliser cd et pwd :

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

utilisant ${BASH_SOURCE[0]} au lieu de "151940920" produit le même comportement indépendamment du fait que le script soit invoqué comme <name> ou source <name>

138
répondu Andrew Norrie 2016-02-18 19:41:47

j'ai juste dû revisiter cette question aujourd'hui et j'ai trouvé https://stackoverflow.com/a/246128/1034080 . Il élabore sur une solution que j'ai utilisé dans le passé ainsi .

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

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

39
répondu Felix Rabe 2017-05-23 12:10:48

obtenir le chemin absolu du script shell

N'utilise pas l'option -f dans readlink, donc devrait fonctionner dans bsd / mac-osx

prend en charge

  • de la source ./ script (Lorsqu'il est appelé par . dot operator)
  • chemin Absolu /chemin/vers/le / script
  • chemin relatif comme ./ script
  • /chemin/dir1/../ dir2/dir3/../ script
  • quand il est appelé de symlink
  • quand le lien symbolique est imbriqué eg) foo->dir1/dir2/bar bar->./../doe doe->script
  • lorsque l'appelant change le nom du script

je cherche des cas en coin où ce code ne fonctionne pas . S'il vous plaît laissez-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`]"

connu issuse

Script doit être sur le disque quelque part , permettez d'être sur un réseau. si vous essayez d'exécuter ce script à partir D'une PIPE, il ne fonctionnera pas

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

Techniquement parlant, il n'est pas défini.

Pratiquement parlant, il n'y a aucun moyen sensé de le détecter. (co-processus ne peut pas accéder env de parent)

35
répondu GreenFox 2013-11-29 18:10:34

Qu'en est-il de l'utilisation:

SCRIPT_PATH=$(dirname `which "151900920"`)

which imprime pour stdout le chemin complet de l'exécutable qui aurait été exécuté lorsque l'argument passé avait été entré à l'invite de shell (ce qui est ce que 0 $contient)

dirname supprime le suffixe Non répertoire du nom de fichier

par conséquent, ce que vous obtenez est le chemin complet vers le script, peu importe si le chemin a été spécifié ou non.

30
répondu Matt 2014-06-04 18:21:05

comme realpath n'est pas installé par défaut sur mon système Linux les travaux suivants pour moi:

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

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

avant d'utiliser ceci lire les commentaires de cette réponse .

24
répondu ypid 2017-05-23 12:02:48

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

SCRIPT=$( readlink -m $( type -p "151900920" ))      # Full path to script
BASE_DIR=`dirname ${SCRIPT}`                # Directory script is run in
NAME=`basename ${SCRIPT}`                   # Actual name of script even if linked
11
répondu Stormcloud 2014-03-24 16:11:39

nous avons placé notre propre produit realpath-lib sur GitHub pour un usage communautaire libre et gratuit.

bouchon honteux mais avec cette bibliothèque de Bash vous pouvez:

get_realpath <absolute|relative|symlink|local file>

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

function get_realpath() {

if [[ -f "" ]]
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 pas de dépendances externes, juste Bash 4+. Contient également des fonctions pour get_dirname , get_filename , get_stemname et validate_path validate_realpath . Il est gratuit, propre, simple et bien documenté, donc il peut être utilisé à des fins d'apprentissage aussi, et sans doute peut être améliorée. Essayez à travers les plates-formes.

mise à jour: après quelques révisions et essais, nous avons remplacé la fonction ci-dessus par quelque chose qui permet d'obtenir le même résultat (sans utiliser dirname, seulement Pure Bash) mais avec une meilleure efficacité:

function get_realpath() {

    [[ ! -f "" ]] && 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

}

comprend également un environnement no_symlinks qui fournit la capacité de résoudre des liens symboliques au système physique. Par défaut, il maintient les liens symboliques intacts.

7
répondu AsymLabs 2013-10-05 13:15:16

vous pouvez essayer de définir la variable suivante:

CWD="$(cd -P -- "$(dirname -- ""151900920"")" && pwd -P)"

ou vous pouvez essayer la fonction suivante dans bash:

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

cette fonction prend 1 argument. Si l'argument a déjà le chemin absolu, imprimez-le tel quel, sinon Imprimez $PWD variable + argument filename (sans préfixe ./ ).

Related:

5
répondu kenorb 2017-05-23 12:18:23

voie conforme sh:

SCRIPT_HOME=`dirname "151900920" | while read a; do cd $a && pwd && break; done`
5
répondu Haruo Kinoshita 2016-11-11 18:48:34

simplement: BASEDIR=$(readlink -f "151900920" | xargs dirname)

pas de fantaisie, les opérateurs

HIH.

4
répondu poussma 2013-12-03 13:59:03

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

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

étant donné que vous voulez juste canoniser le nom que vous obtenez de concaténer $PWD et 0 $(en supposant que 0 $n'est pas absolu pour commencer) il suffit d'utiliser une série de remplacements regex le long de la ligne de abs_dir=${abs_dir//\/.\//\/} et tel.

Oui, je sais que c'est horrible mais ça va marcher et c'est de la purée.

3
répondu wich 2017-05-23 10:31:37

considérant cette question encore une fois: il ya une solution très populaire qui est référencé dans ce fil qui a son origine ici :

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

je suis resté à l'écart de cette solution en raison de l'utilisation de dirname - il peut présenter des difficultés multiplateformes, en particulier si un script doit être verrouillé pour des raisons de sécurité. Mais comme une alternative Pure Bash, Que diriez-vous d'utiliser:

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

est-ce être une option?

3
répondu AsymLabs 2017-05-23 12:10:48

facile à lire? alternative. Ignore symlinks

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

echo -n "current "
pwd
echo script $currentDir 
3
répondu gerardw 2016-05-12 18:43:39

La solution retenue a l'inconvénient (pour moi) de ne pas être "source":

si vous appelez à partir d'un " source ../../yourScript ", "151920920" serait " bash "!

la fonction suivante (pour bash >= 3.0) me donne le bon chemin, cependant le script peut être appelé (directement ou par source , avec un chemin absolu ou relatif):

(par" droit chemin", je veux dire le chemin absolu complet du script étant appelé , même lorsqu'il est appelé d'un autre chemin, directement ou avec source ")

#!/bin/bash
echo "151900920" executed

function bashscriptpath() {
  local _sp=
  local ascript=""151900920""
  local asp="$(dirname "151900920")"
  #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é est de détecter le " source "case et d'utiliser ${BASH_SOURCE[0]} pour récupérer le script actuel.

2
répondu VonC 2011-06-24 02:45:06

si nous utilisons Bash je crois que c'est le moyen le plus commode car il ne nécessite pas d'appels à des commandes externes:

THIS_PATH="${BASH_SOURCE[0]}";
THIS_DIR=$(dirname $THIS_PATH)
2
répondu Nordlöw 2014-04-18 20:05:31

essayez ceci:

cd $(dirname $([ -L "151900920" ] && readlink -f "151900920" || echo "151900920"))
1
répondu diyism 2014-08-29 03:05:05

juste pour l'enfer de l'enfer que j'ai fait un peu de piratage sur un script qui fait des choses purement textuellement, purement en bash. J'espère que j'ai attrapé tous les cas de bord. 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 seulement la correspondance la plus courte possible, ce qui est un problème pour remplacer /foo/../ comme par exemple /*/../ prendra tout avant lui, pas une seule entrée. Et puisque ces modèles ne sont pas vraiment des regexes Je ne vois pas comment qui peut être fait pour travailler. Voici donc la solution joliment alambiquée que j'ai trouvée, enjoy. ;)

au fait, prévenez-moi si vous trouvez des caisses de bord non fermées.

#!/bin/bash

canonicalize_path() {
  local path=""
  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
répondu wich 2011-01-23 14:53:44

j'ai utilisé l'approche suivante avec succès depuis un certain temps (pas sur OSX cependant) et il utilise seulement shell intégré et manipule le 'source foobar.sh' cas aussi loin que j'ai vu.

un problème avec le code d'exemple (rapidement assemblé) ci-dessous est que la fonction utilise $PWD qui peut ou ne peut pas être correct au moment de l'appel de fonction. Donc qui doit être traité.

#!/bin/bash

function canonical_path() {
  # Handle realtive vs absolute path
  [ ${1:0:1} == '/' ] && x= || x=$PWD/
  # 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
répondu andro 2014-04-18 21:38:57

encore une autre façon de le faire:

shopt -s extglob

selfpath="151900920"
selfdir=${selfpath%%+([!/])}

while [[ -L "$selfpath" ]];do
  selfpath=$(readlink "$selfpath")
  if [[ ! "$selfpath" =~ ^/ ]];then
    selfpath=${selfdir}${selfpath}
  fi
  selfdir=${selfpath%%+([!/])}
done

echo $selfpath $selfdir
-1
répondu Meow 2014-12-19 14:02:50

One liner

`dirname $(realpath "151900920")`
-1
répondu Abhijit 2015-12-18 02:02:32

Plus simplement, c'est ce qui fonctionne pour moi:

MY_DIR=`dirname "151900920"`
source $MY_DIR/_inc_db.sh
-4
répondu Elendurwen 2016-11-15 11:31:48