Comment obtenir le répertoire source d'un script Bash à partir du script lui-même

4957

Comment obtenir le chemin du répertoire dans lequel se trouve un script Bash , à l' intérieur de ce script?

Je veux utiliser un script Bash comme lanceur pour une autre application. Je veux changer le répertoire de travail en celui où se trouve le script Bash, donc je peux opérer sur les fichiers dans ce répertoire, comme ceci:

$ ./application
Jiaaro
la source
69
Aucune des solutions actuelles ne fonctionne s'il y a des sauts de ligne à la fin du nom du répertoire - Ils seront supprimés par la substitution de commandes. Pour contourner ce problème, vous pouvez ajouter un caractère non-retour à la ligne dans la substitution de commande - DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd && echo x)"- et le supprimer sans substitution de commande - DIR="${DIR%x}".
l0b0
80
@ jpmc26 Il existe deux situations très courantes: les accidents et le sabotage. Un script ne devrait pas échouer de manière imprévisible simplement parce que quelqu'un, quelque part, a fait un mkdir $'\n'.
l0b0
24
Quiconque laisse les gens saboter leur système de cette manière ne devrait pas se laisser aller à déceler de tels problèmes ... et encore moins à embaucher des gens capables de faire ce genre d'erreur. Je ne l' ai jamais eu, au cours des 25 années d'utilisation bash, vu ce genre de chose arrive partout .... c'est pourquoi nous avons des choses comme perl et des pratiques telles que la vérification taint (je vais probablement flammé pour dire que :)
osirisgothra
3
@ l0b0 Considérez que vous auriez besoin de la même protection dirnameet que le répertoire pourrait commencer par un -(par exemple --help). DIR=$(reldir=$(dirname -- "$0"; echo x); reldir=${reldir%?x}; cd -- "$reldir" && pwd && echo x); DIR=${DIR%?x}. C'est peut-être exagéré?
Score_Under
56
Je vous conseille vivement de lire cette FAQ Bash sur le sujet.
Rany Albeg Wein

Réponses:

6575
#!/bin/bash

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"

est une ligne utile qui vous donnera le nom de répertoire complet du script, peu importe d'où il est appelé.

Cela fonctionnera tant que le dernier composant du chemin utilisé pour trouver le script n'est pas un lien symbolique (les liens de répertoire sont OK). Si vous souhaitez également résoudre les liens vers le script lui-même, vous avez besoin d'une solution multi-lignes:

#!/bin/bash

SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
  SOURCE="$(readlink "$SOURCE")"
  [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"

Ce dernier travaillera avec toute combinaison d'alias, source, bash -c, liens symboliques, etc.

Attention: si vous accédez cdà un répertoire différent avant d'exécuter cet extrait, le résultat peut être incorrect!

Faites également attention aux $CDPATHgotchas et aux effets secondaires de sortie de stderr si l'utilisateur a outrepassé intelligemment le cd pour rediriger la sortie vers stderr à la place (y compris les séquences d'échappement, comme lors d'un appel update_terminal_cwd >&2sur Mac). L'ajout >/dev/null 2>&1à la fin de votre cdcommande prendra en charge les deux possibilités.

Pour comprendre comment cela fonctionne, essayez d'exécuter ce formulaire plus détaillé:

#!/bin/bash

SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  TARGET="$(readlink "$SOURCE")"
  if [[ $TARGET == /* ]]; then
    echo "SOURCE '$SOURCE' is an absolute symlink to '$TARGET'"
    SOURCE="$TARGET"
  else
    DIR="$( dirname "$SOURCE" )"
    echo "SOURCE '$SOURCE' is a relative symlink to '$TARGET' (relative to '$DIR')"
    SOURCE="$DIR/$TARGET" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
  fi
done
echo "SOURCE is '$SOURCE'"
RDIR="$( dirname "$SOURCE" )"
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
if [ "$DIR" != "$RDIR" ]; then
  echo "DIR '$RDIR' resolves to '$DIR'"
fi
echo "DIR is '$DIR'"

Et il imprimera quelque chose comme:

SOURCE './scriptdir.sh' is a relative symlink to 'sym2/scriptdir.sh' (relative to '.')
SOURCE is './sym2/scriptdir.sh'
DIR './sym2' resolves to '/home/ubuntu/dotfiles/fo fo/real/real1/real2'
DIR is '/home/ubuntu/dotfiles/fo fo/real/real1/real2'
Dave Dopson
la source
27
Vous pouvez fusionner cette approche avec la réponse par user25866 pour arriver à une solution qui fonctionne avec source <script>et bash <script>: DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)".
Dan Moulding,
21
Imprime parfois cdquelque chose sur STDOUT! Par exemple, si votre $CDPATHa .. Pour couvrir ce cas, utilisezDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"
user716468
182
Cette réponse acceptée n'est pas correcte, elle ne fonctionne pas avec les liens symboliques et est trop complexe. dirname $(readlink -f $0)est la bonne commande. Voir gist.github.com/tvlooy/cbfbdb111a4ebad8b93e pour un testcase
tvlooy
167
@tvlooy IMO, votre réponse n'est pas non plus tout à fait correcte, car elle échoue lorsqu'il y a un espace sur le chemin. Contrairement à un caractère de nouvelle ligne, ce n'est pas improbable ni même rare. dirname "$(readlink -f "$0")"n'ajoute pas de complexité et est une mesure juste plus robuste pour un minimum de problèmes.
Adrian Günter du
11
@tvlooy votre commentaire n'est pas compatible avec macOS (ou probablement BSD en général), alors que la réponse acceptée est. readlink -f $0donne readlink: illegal option -- f.
Alexander Ljungberg
876

Utilisation dirname "$0":

#!/bin/bash
echo "The script you are running has basename `basename "$0"`, dirname `dirname "$0"`"
echo "The present working directory is `pwd`"

l'utilisation pwdseule ne fonctionnera pas si vous n'exécutez pas le script à partir du répertoire dans lequel il se trouve.

[matt@server1 ~]$ pwd
/home/matt
[matt@server1 ~]$ ./test2.sh
The script you are running has basename test2.sh, dirname .
The present working directory is /home/matt
[matt@server1 ~]$ cd /tmp
[matt@server1 tmp]$ ~/test2.sh
The script you are running has basename test2.sh, dirname /home/matt
The present working directory is /tmp
mat b
la source
25
Pour une portabilité au-delà de bash, 0 $ n'est pas toujours suffisant. Vous devrez peut-être remplacer "type -p $ 0" pour que cela fonctionne si la commande a été trouvée sur le chemin.
Darron
10
@Darron: vous ne pouvez utiliser que type -psi le script est exécutable. Cela peut également ouvrir un trou subtil si le script est exécuté à l'aide bash test2.shet qu'il existe un autre script avec le même nom exécutable ailleurs.
D.Shawley
90
@ Darron: mais puisque la question est balisée bashet que la ligne de hachage-bang mentionne explicitement, /bin/bashje dirais qu'il est assez sûr de dépendre des bashismes.
Joachim Sauer
34
+1, mais le problème avec l'utilisation dirname $0est que si le répertoire est le répertoire courant, vous obtiendrez .. C'est bien, sauf si vous allez changer de répertoire dans le script et vous attendre à utiliser le chemin que vous avez obtenu dirname $0comme s'il était absolu. Pour obtenir le chemin absolu: pushd `dirname $0` > /dev/null, SCRIPTPATH=`pwd`, popd > /dev/null: pastie.org/1489386 (Mais sûrement il y a une meilleure façon d'élargir cette voie?)
TJ Crowder
9
@TJ Crowder Je ne suis pas sûr que ce dirname $0soit un problème si vous l'assignez à une variable et l'utilisez ensuite pour lancer un script comme $dir/script.sh; J'imagine que c'est le cas d'utilisation pour ce type de chose dans 90% des cas. ./script.shfonctionnerait bien.
mat le
516

La dirnamecommande est la plus simple, en analysant simplement le chemin jusqu'au nom de fichier de la $0variable (nom du script):

dirname "$0"

Mais, comme l'a souligné matt b , le chemin renvoyé est différent selon la façon dont le script est appelé. pwdne fait pas le travail car cela vous indique uniquement le répertoire actuel, pas le répertoire dans lequel se trouve le script. De plus, si un lien symbolique vers un script est exécuté, vous obtiendrez un chemin (probablement relatif) vers où réside le lien, pas le script réel.

Certains autres ont mentionné la readlinkcommande, mais dans sa plus simple expression, vous pouvez utiliser:

dirname "$(readlink -f "$0")"

readlinkrésoudra le chemin du script en chemin absolu depuis la racine du système de fichiers. Ainsi, tous les chemins contenant des points simples ou doubles, des tildes et / ou des liens symboliques seront résolus en un chemin complet.

Voici un script illustrant chacun de ces éléments whatdir.sh:

#!/bin/bash
echo "pwd: `pwd`"
echo "\$0: $0"
echo "basename: `basename $0`"
echo "dirname: `dirname $0`"
echo "dirname/readlink: $(dirname $(readlink -f $0))"

Exécuter ce script dans mon répertoire personnel, en utilisant un chemin relatif:

>>>$ ./whatdir.sh 
pwd: /Users/phatblat
$0: ./whatdir.sh
basename: whatdir.sh
dirname: .
dirname/readlink: /Users/phatblat

Encore une fois, mais en utilisant le chemin d'accès complet au script:

>>>$ /Users/phatblat/whatdir.sh 
pwd: /Users/phatblat
$0: /Users/phatblat/whatdir.sh
basename: whatdir.sh
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat

Modification des répertoires:

>>>$ cd /tmp
>>>$ ~/whatdir.sh 
pwd: /tmp
$0: /Users/phatblat/whatdir.sh
basename: whatdir.sh
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat

Et enfin en utilisant un lien symbolique pour exécuter le script:

>>>$ ln -s ~/whatdir.sh whatdirlink.sh
>>>$ ./whatdirlink.sh 
pwd: /tmp
$0: ./whatdirlink.sh
basename: whatdirlink.sh
dirname: .
dirname/readlink: /Users/phatblat
phatblat
la source
13
readlinkne sera pas disponible sur certaines plates-formes dans l'installation par défaut. Essayez d'éviter de l'utiliser si vous le pouvez
TL
43
attention à tout citer pour éviter les problèmes d'espaces:export SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
Catskul
13
Dans OSX, Yosemite 10.10.1 -fn'est pas reconnu comme une option pour readlink. Utiliser à la stat -fplace fait le travail. Merci
cucu8
11
Dans OSX, il y en a greadlink, qui est fondamentalement le que readlinknous connaissons tous. Voici une version indépendante de la plateforme:dir=`greadlink -f ${BASH_SOURCE[0]} || readlink -f ${BASH_SOURCE[0]}`
robert
6
Bon appel, @robert. FYI, greadlinkpeut facilement être installé via homebrew:brew install coreutils
phatblat
184
pushd . > /dev/null
SCRIPT_PATH="${BASH_SOURCE[0]}"
if ([ -h "${SCRIPT_PATH}" ]); then
  while([ -h "${SCRIPT_PATH}" ]); do cd `dirname "$SCRIPT_PATH"`; 
  SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
fi
cd `dirname ${SCRIPT_PATH}` > /dev/null
SCRIPT_PATH=`pwd`;
popd  > /dev/null

Fonctionne pour toutes les versions, y compris

  • lorsqu'il est appelé via un lien souple de profondeur multiple,
  • quand le déposer
  • lorsque le script est appelé par la commande " source" aka .(point).
  • lorsque arg $0est modifié depuis l'appelant.
  • "./script"
  • "/full/path/to/script"
  • "/some/path/../../another/path/script"
  • "./some/folder/script"

Alternativement, si le script bash lui-même est un lien symbolique relatif, vous voulez le suivre et renvoyer le chemin complet du script lié:

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

SCRIPT_PATHest donné en chemin complet, peu importe comment il est appelé.
Assurez-vous simplement de localiser cela au début du script.

Ce commentaire et code Copyleft, licence sélectionnable sous GPL2.0 ou ultérieure ou CC-SA 3.0 (CreativeCommons Share Alike) ou ultérieure. (c) 2008. Tous droits réservés. Aucune garantie d'aucune sorte. Tu étais prévenu.
http://www.gnu.org/licenses/gpl-2.0.txt
http://creativecommons.org/licenses/by-sa/3.0/
18eedfe1c99df68dc94d4a94712a71aaa8e1e9e36cacf421b9463dd2bbaa02906d0d6656

utilisateur25866
la source
4
Agréable! Pourrait être raccourci en remplaçant "pushd [...] popd / dev / null" par SCRIPT_PATH = readlink -f $(dirname "${VIRTUAL_ENV}");
e-satis
5
Et au lieu d'utiliser pushd ...; ne serait-il pas préférable d'utiliser $ (cd dirname "${SCRIPT_PATH}"&& pwd)? Mais de toute façon, un excellent script!
ovanes
6
Il est dangereux pour un script de cdsortir de son répertoire actuel dans l'espoir d'y cdrevenir plus tard: le script peut ne pas avoir l'autorisation de revenir au répertoire qui était en cours lors de son appel. (Idem pour pushd / popd)
Adrian Pronk
7
readlink -fest spécifique à GNU. BSD readlinkn'a pas cette option.
Kara Brightwell
3
Qu'y a-t-il avec tous les sous-coquilles inutiles? ([ ... ])est moins efficace que [ ... ], et il n'y a aucun avantage tiré de l'isolement offert en échange de cette performance frappée ici.
Charles Duffy
110

Réponse courte:

`dirname $0`

ou (de préférence ):

$(dirname "$0")
Fabien
la source
17
Cela ne fonctionnera pas si vous sourcez le script. "source my / script.sh"
Arunprasad Rajkumar
Je l'utilise tout le temps dans mes scripts bash qui automatisent des trucs et invoquent souvent d'autres scripts dans le même répertoire. Je ne les utiliserais jamais sourceet cd $(dirname $0)c'est facile à retenir.
kqw
16
@vidstige: ${BASH_SOURCE[0]}au lieu de $0fonctionnera avecsource my/script.sh
Timothy Jones
@TimothyJones qui échouera 100% du temps s'il provient d'un autre shell que bash. ${BASH_SOURCE[0]}n'est pas satisfaisant du tout. ${BASH_SOURCE:-0}c'est beaucoup mieux.
Mathieu CAROFF
106

Vous pouvez utiliser $BASH_SOURCE:

#!/bin/bash

scriptdir=`dirname "$BASH_SOURCE"`

Notez que vous devez utiliser #!/bin/bashet non #!/bin/shcar c'est une extension Bash.

Mr Shark
la source
14
Quand je le fais ./foo/script, alors $(dirname $BASH_SOURCE)c'est ./foo.
jusqu'au
1
@Till, Dans ce cas, nous pouvons utiliser la realpathcommande pour obtenir le chemin complet de ./foo/script. Ainsi dirname $(realpath ./foo/script) donnera le chemin du script.
purushothaman poovai
73

Cela devrait le faire:

DIR="$(dirname "$(readlink -f "$0")")"

Cela fonctionne avec les liens symboliques et les espaces dans le chemin.

Voir les pages de manuel pour dirnameet readlink.

D'après la piste des commentaires, il ne semble pas fonctionner avec Mac OS. Je n'ai pas d'idées pourquoi c'est comme ça. Aucune suggestion?

Simon Rigét
la source
6
avec votre solution, en invoquant le script comme ./script.shshows .au lieu du chemin de répertoire complet
Bruno Negrão Zica
5
Il n'y a pas d'option -f pour readlink sur MacOS. Utilisez statplutôt. Mais quand même, cela montre .si vous êtes dans «ce» répertoire.
Denis The Menace
2
Vous devez installer à coreutilspartir de Homebrew et utiliser greadlinkpour obtenir l' -foption sur MacOS car c'est * BSD sous les couvertures et non Linux.
dragon788
Vous devez ajouter des guillemets doubles entourant tout le côté droit:DIR="$(dirname "$(readlink -f "$0")")"
hagello
60

pwdpeut être utilisé pour trouver le répertoire de travail actuel et dirnamepour trouver le répertoire d'un fichier particulier (la commande qui a été exécutée est $0, dirname $0devrait donc vous donner le répertoire du script actuel).

Cependant, dirnamedonne précisément la partie répertoire du nom de fichier, qui sera plus que probablement relative au répertoire de travail actuel. Si votre script doit changer de répertoire pour une raison quelconque, la sortie de dirnamedevient vide de sens.

Je suggère ce qui suit:

#!/bin/bash

reldir=`dirname $0`
cd $reldir
directory=`pwd`

echo "Directory is $directory"

De cette façon, vous obtenez un répertoire absolu plutôt que relatif.

Étant donné que le script sera exécuté dans une instance bash distincte, il n'est pas nécessaire de restaurer le répertoire de travail par la suite, mais si vous souhaitez revenir dans votre script pour une raison quelconque, vous pouvez facilement attribuer la valeur de pwdà une variable avant de changer de répertoire, pour une utilisation future.

Bien que juste

cd `dirname $0`

résout le scénario spécifique dans la question, je trouve avoir le chemin absolu vers plus plus utile en général.

SpoonMeiser
la source
9
Vous pouvez tout faire en une seule ligne comme celle-ci: RÉPERTOIRE = $ (cd dirname $0&& pwd)
dogbane
Cela ne fonctionne pas si le script source un autre script et que vous souhaitez connaître le nom de ce dernier.
reinierpost
52

Je suis fatigué de revenir sur cette page encore et encore pour copier-coller le one-liner dans la réponse acceptée. Le problème, c'est qu'il n'est pas facile à comprendre et à retenir.

Voici un script facile à retenir:

DIR="$(dirname "${BASH_SOURCE[0]}")"  # get the directory name
DIR="$(realpath "${DIR}")"    # resolve its full path if need be
Thamme Gowda
la source
2
Ou, plus obscurément, sur une seule ligne: DIR=$(realpath "$(dirname "${BASH_SOURCE[0]}")")
agc
Pourquoi n'est-ce pas la réponse acceptée? Y a-t-il une différence realpathentre la résolution «manuelle» et la boucle de readlink? Même la readlinkpage de manuel ditNote realpath(1) is the preferred command to use for canonicalization functionality.
User9123
1
Et au fait ne devrions-nous pas postuler realpathavant dirname, pas après? Si le fichier script lui-même est un lien symbolique ... Cela donnerait quelque chose comme DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")". En fait très proche de la réponse proposée par Simon.
User9123
@ User9123 Je pense que l'accepter est d'essayer d'être compatible avec tous les shell / distro populaires. De plus, selon ce que vous essayez de faire, dans la plupart des cas, les gens veulent obtenir le répertoire où se trouve le lien symbolique au lieu du répertoire de la source réelle.
Wang
37

Je ne pense pas que ce soit aussi facile que d'autres l'ont fait croire. pwdne fonctionne pas, car le répertoire courant n'est pas nécessairement le répertoire avec le script. $0n'a pas toujours les informations non plus. Considérez les trois façons suivantes pour appeler un script:

./script

/usr/bin/script

script

Dans la première et la troisième manière, $0les informations de chemin d'accès ne sont pas complètes. Dans les deuxième et troisième, pwdne fonctionne pas. La seule façon d'obtenir le répertoire de la troisième manière serait de parcourir le chemin et de trouver le fichier avec la bonne correspondance. Fondamentalement, le code devrait refaire ce que fait le système d'exploitation.

Une façon de faire ce que vous demandez serait de simplement coder en dur les données dans le /usr/sharerépertoire et de les référencer par son chemin complet. Les données ne doivent de /usr/bintoute façon pas être dans le répertoire, c'est donc probablement la chose à faire.

Jim
la source
9
Si vous avez l'intention de réfuter son commentaire, PROUVEZ qu'un script PEUT accéder à l'endroit où il est stocké avec un exemple de code.
Richard Duerr
34
SCRIPT_DIR=$( cd ${0%/*} && pwd -P )
PM
la source
C'est beaucoup plus court que la réponse choisie. Et semble fonctionner aussi bien. Cela mérite 1000 votes pour que les gens ne l'ignorent pas.
Patrick
2
Comme la plupart des réponses précédentes l'expliquent en détail, ni $0ni ne pwdsont garantis d'avoir les bonnes informations, selon la façon dont le script est appelé.
IMSoP
34
$(dirname "$(readlink -f "$BASH_SOURCE")")
11
la source
Je préfère, $BASH_SOURCEplus $0, car c'est explicite même pour les lecteurs qui ne connaissent pas bien bash. $(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
blobmaster
32

Cela obtient le répertoire de travail actuel sur Mac OS X 10.6.6:

DIR=$(cd "$(dirname "$0")"; pwd)
Pubguy
la source
27

Ceci est spécifique à Linux, mais vous pouvez utiliser:

SELF=$(readlink /proc/$$/fd/255)
Steve Baker
la source
1
C'est aussi spécifique à bash, mais peut-être que le comportement de bash a changé? /proc/fd/$$/255semble pointer vers le tty, pas vers un répertoire. Par exemple, dans mon shell de connexion actuel, les descripteurs de fichiers 0, 1, 2 et 255 font tous référence à /dev/pts/4. Dans tous les cas, le manuel bash ne mentionne pas le fd 255, il est donc peu judicieux de dépendre de ce comportement. \
Keith Thompson
2
Shell interactif! = Script. Quoi realpath ${BASH_SOURCE[0]};qu'il en soit, cela semble être la meilleure façon de procéder.
Steve Baker
23

Voici une doublure conforme POSIX:

SCRIPT_PATH=`dirname "$0"`; SCRIPT_PATH=`eval "cd \"$SCRIPT_PATH\" && pwd"`

# test
echo $SCRIPT_PATH
lamawithonel
la source
4
J'ai réussi avec cela lors de l'exécution d'un script seul ou en utilisant sudo, mais pas lors de l'appel à source ./script.sh
Michael R
Et il échoue lorsqu'il cdest configuré pour imprimer le nouveau nom de chemin.
Aaron Digulla
18

J'ai essayé tout cela et aucun n'a fonctionné. L'un était très proche mais avait un minuscule bug qui le cassait gravement; ils ont oublié de mettre le chemin entre guillemets.

De plus, beaucoup de gens supposent que vous exécutez le script à partir d'un shell, alors ils oublient que lorsque vous ouvrez un nouveau script, il revient par défaut à votre domicile.

Essayez ce répertoire sur pour la taille:

/var/No one/Thought/About Spaces Being/In a Directory/Name/And Here's your file.text

Cela vous convient, peu importe comment ou où vous l'exécutez:

#!/bin/bash
echo "pwd: `pwd`"
echo "\$0: $0"
echo "basename: `basename "$0"`"
echo "dirname: `dirname "$0"`"

Donc, pour le rendre réellement utile, voici comment passer au répertoire du script en cours d'exécution:

cd "`dirname "$0"`"
Mike Bethany
la source
4
Ne fonctionne pas si le script provient d'un autre script.
reinierpost
Cela ne fonctionne pas si la dernière partie de $ 0 est un lien symbolique pointant vers une entrée d'un autre répertoire ( ln -s ../bin64/foo /usr/bin/foo).
hagello
17

Voici la manière simple et correcte:

actual_path=$(readlink -f "${BASH_SOURCE[0]}")
script_dir=$(dirname "$actual_path")

Explication:

  • ${BASH_SOURCE[0]}- le chemin complet vers le script. La valeur de ceci sera correcte même lorsque le script est généré, par exemple, source <(echo 'echo $0')imprime bash , tandis que le remplacer par ${BASH_SOURCE[0]}affichera le chemin complet du script. (Bien sûr, cela suppose que vous êtes OK en prenant une dépendance à Bash.)

  • readlink -f- Résout récursivement tous les liens symboliques dans le chemin spécifié. Il s'agit d'une extension GNU et non disponible sur (par exemple) les systèmes BSD. Si vous utilisez un Mac, vous pouvez utiliser Homebrew pour installer GNU coreutilset le remplacer par greadlink -f.

  • Et bien sûr, dirnameobtient le répertoire parent du chemin.

James Ko
la source
1
greadlink -fmalheureusement, ne fonctionne pas efficacement lors de sourcel'
ingénierie
17

La façon la plus courte et la plus élégante de le faire est:

#!/bin/bash
DIRECTORY=$(cd `dirname $0` && pwd)
echo $DIRECTORY

Cela fonctionnerait sur toutes les plateformes et est super propre.

Plus de détails peuvent être trouvés dans "Dans quel répertoire se trouve ce script bash? ".

Atul
la source
grande solution propre, mais cela ne fonctionnera pas si le fichier est lié par un lien symbolique.
ruuter
16

J'utiliserais quelque chose comme ça:

# retrieve the full pathname of the called script
scriptPath=$(which $0)

# check whether the path is a link or not
if [ -L $scriptPath ]; then

    # it is a link then retrieve the target path and get the directory name
    sourceDir=$(dirname $(readlink -f $scriptPath))

else

    # otherwise just get the directory name of the script path
    sourceDir=$(dirname $scriptPath)

fi
Nicolas
la source
Ceci est le vrai! Fonctionne avec simple shaussi! Problème avec des dirname "$0"solutions simples : si le script se trouve dans le $PATHet est invoqué sans chemin, ils donneront un résultat incorrect.
Notinlist
@Notinlist Pas le cas. Si le script est trouvé via le PATH, $0contiendra le nom de fichier absolu. Si le script est invoqué avec un nom de fichier relatif ou absolu contenant un /, $0le contiendra.
Neil Mayhew
Ne fonctionnera pas pour un script d'origine.
Amit Naidu
16

Il s'agit d'une légère révision de la solution e-satis et 3bcdnlklvc04a soulignée dans leur réponse :

SCRIPT_DIR=''
pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && {
    SCRIPT_DIR="$PWD"
    popd > /dev/null
}    

Cela devrait toujours fonctionner dans tous les cas énumérés.

Cela évitera popdaprès un échec pushd, grâce à konsolebox.

Fuwjax
la source
Cela fonctionne parfaitement pour obtenir le "vrai" nom de répertoire, plutôt que simplement le nom d'un lien symbolique. Je vous remercie!
Jay Taylor
1
MieuxSCRIPT_DIR=''; pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && { SCRIPT_DIR=$PWD; popd > /dev/null; }
konsolebox
@konsolebox, contre quoi essayez-vous de vous défendre? Je suis généralement un fan des conditions logiques intégrées, mais quelle était l'erreur spécifique que vous voyiez dans le pushd? Je préfère trouver un moyen de le gérer directement au lieu de renvoyer un SCRIPT_DIR vide.
Fuwjax
@Fuwjax Pratique naturelle pour éviter de faire popddans les cas (même rares) où pushdéchoue. Et en cas d' pushdéchec, quelle devrait être, selon vous, la valeur SCRIPT_DIR? L'action peut varier en fonction de ce qui peut sembler logique ou de ce qu'un utilisateur pourrait préférer, mais il est certain que faire popdest mal.
konsolebox
Tous ces pushd popddangers pourraient être évités simplement en les supprimant et en utilisant à la place cd+ pwdenfermé dans une substitution de commande. SCRIPT_DIR=$(...)
Amit Naidu
16

Pour les systèmes ayant des coreutils GNU readlink(par ex. Linux):

$(readlink -f "$(dirname "$0")")

Il n'est pas nécessaire d'utiliser BASH_SOURCElorsque $0contient le nom de fichier du script.

utilisateur1338062
la source
3
sauf si le script provient de. ou 'source', auquel cas ce sera toujours le script qui l'a fourni, ou, si à partir de la ligne de commande, '-bash' (connexion tty) ou 'bash' (invoqué via 'bash -l') ou '/ bin / bash '(invoqué comme un shell interactif sans connexion)
osirisgothra
J'ai ajouté une deuxième paire de citations autour de l' dirnameappel. Nécessaire si le chemin du répertoire contient des espaces.
user1338062
14
#!/bin/sh
PRG="$0"

# need this for relative symlinks
while [ -h "$PRG" ] ; do
   PRG=`readlink "$PRG"`
done

scriptdir=`dirname "$PRG"`
Le petit singe
la source
Je ne l'ai pas testé sur différents systèmes. Mais cette solution est celle qui fonctionne tout de suite au moins sur Ubuntu, pour moi!
Natus Drew
$0ne fonctionnera pas pour un script d'origine
Amit Naidu
13

$_mérite d'être mentionné comme une alternative à $0. Si vous exécutez un script à partir de Bash, la réponse acceptée peut être raccourcie en:

DIR="$( dirname "$_" )"

Notez que cela doit être la première instruction de votre script.

hurrymaplelad
la source
4
Il casse si vous sourceou .le script. Dans ces situations, $_contiendrait le dernier paramètre de la dernière commande que vous avez exécutée avant le .. $BASH_SOURCEfonctionne à chaque fois.
clacke
11

J'ai comparé bon nombre des réponses données et j'ai trouvé des solutions plus compactes. Ceux-ci semblent gérer tous les cas de bord fou qui découlent de votre combinaison préférée de:

  • Chemins absolus ou chemins relatifs
  • Liens logiciels de fichiers et de répertoires
  • Comme l' invocation script, bash script, bash -c script, source scriptou. script
  • Espaces, tabulations, sauts de ligne, unicode, etc. dans les répertoires et / ou le nom de fichier
  • Noms de fichiers commençant par un tiret

Si vous exécutez sous Linux, il semble que l'utilisation du prochandle soit la meilleure solution pour localiser la source entièrement résolue du script en cours d'exécution (dans une session interactive, le lien pointe vers le respectif /dev/pts/X):

resolved="$(readlink /proc/$$/fd/255 && echo X)" && resolved="${resolved%$'\nX'}"

Cela a un peu de laideur, mais le correctif est compact et facile à comprendre. Nous n'utilisons pas uniquement les primitives bash, mais je suis d'accord avec cela car cela readlinksimplifie considérablement la tâche. Le echo Xajoute un Xà la fin de la chaîne de variable afin que les espaces de fin du nom de fichier ne soient pas consommés et que la substitution de paramètre ${VAR%X}à la fin de la ligne supprime le X. Parce qu'il readlinkajoute une nouvelle ligne qui lui est propre (qui serait normalement consommée dans la substitution de commandes si ce n'était pour notre supercherie précédente), nous devons également nous en débarrasser. Ceci est plus facilement accompli en utilisant le $''schéma de citation, qui nous permet d'utiliser des séquences d'échappement telles que\n pour représenter les retours à la ligne (c'est aussi ainsi que vous pouvez facilement créer des répertoires et des fichiers nommés de manière détournée).

Ce qui précède devrait couvrir vos besoins pour localiser le script en cours d'exécution sur Linux, mais si vous n'avez pas le procsystème de fichiers à votre disposition, ou si vous essayez de localiser le chemin entièrement résolu d'un autre fichier, alors peut-être que vous trouver le code ci-dessous utile. Ce n'est qu'une légère modification par rapport à la doublure ci-dessus. Si vous jouez autour avec répertoire / étranges noms de fichiers, en vérifiant la sortie à la fois lset readlinkest informative, comme lsAffichera chemins « simplifiée », en substituant ?pour des choses comme les nouvelles lignes.

absolute_path=$(readlink -e -- "${BASH_SOURCE[0]}" && echo x) && absolute_path=${absolute_path%?x}
dir=$(dirname -- "$absolute_path" && echo x) && dir=${dir%?x}
file=$(basename -- "$absolute_path" && echo x) && file=${file%?x}

ls -l -- "$dir/$file"
printf '$absolute_path: "%s"\n' "$absolute_path"
billyjmc
la source
Je reçois /dev/pts/30avec bash sur Ubuntu 14.10 Desktop.
Dan Dascalescu
@DanDascalescu Vous utilisez le one-liner? Ou l'extrait de code complet en bas? Et lui donniez-vous des noms de chemin difficiles?
billyjmc
La ligne un plus une autre ligne echo $resolved, je sauvé comme d, chmod +x d, ./d.
Dan Dascalescu
@DanDascalescu La première ligne de votre script doit être#!/bin/bash
billyjmc
10

Essayez d'utiliser:

real=$(realpath $(dirname $0))
eeerahul
la source
1
Tout ce que je veux savoir, c'est pourquoi cette voie n'est pas bonne? Cela ne me semblait pas mauvais et correct. Quelqu'un pourrait-il expliquer pourquoi il a été rejeté?
Shou Ya
7
realpath n'est pas un utilitaire standard.
Steve Bennett
2
Sous Linux, realpath est un utilitaire standard (faisant partie du paquet GNU coreutils), mais ce n'est pas un bash intégré (c'est-à-dire une fonction fournie par bash lui-même). Si vous utilisez Linux, cette méthode fonctionnera probablement, bien que je remplacerais le $0pour ${BASH_SOURCE[0]}que cette méthode fonctionne n'importe où, y compris dans une fonction.
Doug Richardson
3
L'ordre des opérations dans cette réponse est incorrect. Vous devez d' abord résoudre le lien symbolique, puis le faire dirnamecar la dernière partie de $0peut être un lien symbolique pointant vers un fichier qui ne se trouve pas dans le même répertoire que le lien symbolique lui-même. La solution décrite dans cette réponse obtient simplement le chemin du répertoire où le lien symbolique qu'il a stocké, pas le répertoire de la cible. De plus, cette solution manque de citation. Cela ne fonctionnera pas si le chemin contient des caractères spéciaux.
hagello
3
dir="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"
Kostiantyn Ponomarenko
9

Essayez la solution de compatibilité croisée suivante:

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

car les commandes telles que realpathou readlinkpourraient ne pas être disponibles (selon le système d'exploitation).

Remarque: Dans Bash, il est recommandé d'utiliser à la ${BASH_SOURCE[0]}place de $0, sinon le chemin peut se casser lors de la recherche du fichier ( source/ .).

Vous pouvez également essayer la fonction suivante dans bash:

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

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

En relation:

kenorb
la source
Veuillez expliquer davantage la fonction realpath.
Chris
1
La realpathfonction @Chris prend 1 argument. Si l'argument a déjà un chemin absolu, imprimez-le tel quel, sinon imprimez $PWD+ nom de fichier (sans ./préfixe).
kenorb
Votre solution de compatibilité croisée ne fonctionne pas lorsque le script est lié par un lien symbolique.
Jakub Jirutka
9

Je crois que j'ai celui-ci. Je suis en retard à la fête, mais je pense que certains apprécieront d'être ici s'ils rencontrent ce fil. Les commentaires doivent expliquer:

#!/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
8

Ce sont de courtes façons d'obtenir des informations sur le script:

Dossiers et fichiers:

    Script: "/tmp/src dir/test.sh"
    Calling folder: "/tmp/src dir/other"

En utilisant ces commandes:

    echo Script-Dir : `dirname "$(realpath $0)"`
    echo Script-Dir : $( cd ${0%/*} && pwd -P )
    echo Script-Dir : $(dirname "$(readlink -f "$0")")
    echo
    echo Script-Name : `basename "$(realpath $0)"`
    echo Script-Name : `basename $0`
    echo
    echo Script-Dir-Relative : `dirname "$BASH_SOURCE"`
    echo Script-Dir-Relative : `dirname $0`
    echo
    echo Calling-Dir : `pwd`

Et j'ai obtenu cette sortie:

     Script-Dir : /tmp/src dir
     Script-Dir : /tmp/src dir
     Script-Dir : /tmp/src dir

     Script-Name : test.sh
     Script-Name : test.sh

     Script-Dir-Relative : ..
     Script-Dir-Relative : ..

     Calling-Dir : /tmp/src dir/other

Voir également: https://pastebin.com/J8KjxrPF

Utilisateur8461
la source
Je pense que ma réponse est correcte car il est difficile de trouver une édition de travail simple. Ici, vous pouvez prendre le code que vous aimez, par exemple cd + pwd, dirname + realpath ou dirname + readlink. Je ne suis pas sûr que toutes les parties existent avant et la plupart des réponses sont complexes et surchargées. Ici, vous pouvez afficher le code que vous souhaitez utiliser. Au moins s'il vous plaît ne le supprimez pas car j'ai besoin à l'avenir: D
User8461
8

Cela fonctionne dans bash-3.2:

path="$( dirname "$( which "$0" )" )"

Si vous avez un ~/binrépertoire dans votre $PATH, vous en avez Adans ce répertoire. Il source le script ~/bin/lib/B. Vous savez où le script inclus est relatif à celui d'origine, dans le libsous - répertoire, mais pas où il est relatif au répertoire actuel de l'utilisateur.

Ceci est résolu par ce qui suit (à l'intérieur A):

source "$( dirname "$( which "$0" )" )/lib/B"

Peu importe où se trouve l'utilisateur ou comment il appelle le script, cela fonctionnera toujours.

Matt Tardiff
la source
3
Le point whichest très discutable. type, hashEt d' autres fonctions intégrées font la même chose de mieux dans bash. whichest un peu plus portable, bien qu'il ne soit pas vraiment le même que celui whichutilisé dans d'autres shells comme tcsh, qui en fait un intégré.
Réinstallez Monica, le
"Toujours"? Pas du tout. whichétant un outil externe, vous n'avez aucune raison de croire qu'il se comporte de manière identique au shell parent.
Charles Duffy
7

La meilleure solution compacte à mon avis serait:

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

Il n'y a aucune dépendance à autre chose que Bash. L'utilisation de dirname, readlinket basenameconduira éventuellement à des problèmes de compatibilité, il est donc préférable d'éviter autant que possible.

AsymLabs
la source
2
Vous devriez probablement ajouter slash que: "$( cd "$( echo "${BASH_SOURCE[0]%/*}/" )"; pwd )". Vous auriez des problèmes avec le répertoire racine si vous ne le faites pas. Aussi pourquoi devez-vous même utiliser l'écho?
konsolebox
dirnameet basenamesont POSIX standardisés, alors pourquoi éviter de les utiliser? Liens: dirname,basename
myrdd
Empêcher deux fourches de processus supplémentaires et s'en tenir aux composants internes de la coque peut être une des raisons.
Amit Naidu