Script shell Unix savoir dans quel répertoire se trouve le fichier de script?

511

Fondamentalement, j'ai besoin d'exécuter le script avec des chemins d'accès liés à l'emplacement du fichier de script shell, comment puis-je changer le répertoire actuel dans le même répertoire que celui où réside le fichier de script?

William Yeung
la source
12
Est-ce vraiment un doublon? Cette question concerne un "script shell unix", l'autre spécifiquement sur Bash.
michaeljt
2
@BoltClock: cette question a été incorrectement fermée. La question liée concerne Bash. Cette question concerne la programmation shell Unix. Notez que les réponses acceptées sont assez différentes!
Dietrich Epp
@Dietrich Epp: Vous avez raison. Il semble que le choix du demandeur de la réponse acceptée et l'ajout de la balise [bash] (probablement en réponse à cela) m'ont amené à marquer la question comme un doublon en réponse à un indicateur.
BoltClock
2
Je pense que cette réponse est meilleure: obtenir le répertoire source d'un script Bash de l'intérieur
Alan Zhiliang Feng

Réponses:

568

Dans Bash, vous devriez obtenir ce dont vous avez besoin comme ceci:

#!/usr/bin/env bash

BASEDIR=$(dirname "$0")
echo "$BASEDIR"
TheMarko
la source
17
Cela ne fonctionne pas si vous avez appelé le script via un lien symbolique dans un répertoire différent. Pour que cela fonctionne, vous devez également l'utiliser readlink(voir la réponse de al ci-dessous)
AndrewR
44
En bash, il est plus sûr d'utiliser $BASH_SOURCEà la place de $0, car $0il ne contient pas toujours le chemin du script invoqué, comme lors de la «sourcing» d'un script.
mklement0
2
$BASH_SOURCEest spécifique à Bash, la question concerne le script shell en général.
Ha-Duong Nguyen
7
@auraham: CUR_PATH=$(pwd)ou pwdrenvoyez le répertoire courant (qui ne doit pas nécessairement être le répertoire parent des scripts)!
Andreas Dietrich
5
J'ai essayé la méthode @ mklement0 recommandée, en utilisant $BASH_SOURCE, et elle renvoie ce dont j'avais besoin. Mon script est appelé à partir d'un autre script et $0renvoie .alors que $BASH_SOURCErenvoie le sous-répertoire de droite (dans mon cas scripts).
David Rissato Cruz
402

Le message d'origine contient la solution (ignorez les réponses, elles n'ajoutent rien d'utile). Le travail intéressant est effectué par la commande unix mentionnée readlinkavec option -f. Fonctionne lorsque le script est appelé par un chemin absolu ainsi que par un chemin relatif.

Pour bash, sh, ksh:

#!/bin/bash 
# Absolute path to this script, e.g. /home/user/bin/foo.sh
SCRIPT=$(readlink -f "$0")
# Absolute path this script is in, thus /home/user/bin
SCRIPTPATH=$(dirname "$SCRIPT")
echo $SCRIPTPATH

Pour tcsh, csh:

#!/bin/tcsh
# Absolute path to this script, e.g. /home/user/bin/foo.csh
set SCRIPT=`readlink -f "$0"`
# Absolute path this script is in, thus /home/user/bin
set SCRIPTPATH=`dirname "$SCRIPT"`
echo $SCRIPTPATH

Voir aussi: https://stackoverflow.com/a/246128/59087

Al.
la source
12
Remarque: tous les systèmes ne l'ont pas readlink. C'est pourquoi j'ai recommandé d'utiliser pushd / popd (intégrés pour bash).
docwhat
23
L' -foption de faire readlinkquelque chose de différent sur OS X (Lion) et éventuellement BSD. stackoverflow.com/questions/1055671/…
Ergwun
9
Pour clarifier le commentaire de @ Ergwun: -fn'est pas du tout pris en charge sur OS X (comme pour Lion); là, vous pouvez soit supprimer le -fpour faire avec la résolution d'au plus un niveau d'indirection, par exemple pushd "$(dirname "$(readlink "$BASH_SOURCE" || echo "$BASH_SOURCE")")", ou vous pouvez rouler votre propre script de suivi de lien symbolique récursif comme illustré dans la publication liée.
mklement0
2
Je ne comprends toujours pas pourquoi le PO aurait besoin du chemin absolu. Reporting "." devrait fonctionner correctement si vous souhaitez accéder aux fichiers relatifs au chemin des scripts et que vous avez appelé le script comme ./myscript.sh
Stefan Haberl
10
@StefanHaberl Je pense que ce serait un problème si vous exécutiez le script alors que votre répertoire de travail actuel était différent de l'emplacement du script (par exemple sh /some/other/directory/script.sh), dans ce cas, ce .serait votre pwd, pas/some/other/directory
Jon z
51

Un commentaire antérieur sur une réponse le disait, mais il est facile de manquer parmi toutes les autres réponses.

Lors de l'utilisation de bash:

echo this file: "$BASH_SOURCE"
echo this dir: "$(dirname "$BASH_SOURCE")"

Manuel de référence Bash, 5.2 Variables Bash

Daniel
la source
3
Seul celui-ci fonctionne avec le script dans le chemin de l'environnement, les plus votés ne fonctionnent pas. Merci!
Juillet
Vous devez utiliser à la dirname "$BASH_SOURCE"place pour gérer les espaces dans $ BASH_SOURCE.
Mygod
1
Selon l'outil ShellCheck, une manière plus explicite d'imprimer le répertoire serait: "$ (dirname" $ ​​{BASH_SOURCE {0}} ")" car BASH_SOURCE est un tableau, et sans l'indice, le premier élément est pris par défaut .
Adrian M.
1
@AdrianM. , vous voulez des crochets, pas des accolades, pour l'index:"$(dirname "${BASH_SOURCE[0]}")"
Hashbrown
Pas bon pour moi ... imprime juste un point (c'est-à-dire le répertoire actuel, probablement)
JL_SO
40

En supposant que vous utilisez bash

#!/bin/bash

current_dir=$(pwd)
script_dir=$(dirname $0)

echo $current_dir
echo $script_dir

Ce script doit imprimer le répertoire dans lequel vous vous trouvez, puis le répertoire dans lequel se trouve le script. Par exemple, lorsque vous l'appelez depuis /avec le script dans /home/mez/, il génère

/
/home/mez

N'oubliez pas que lorsque vous affectez des variables à la sortie d'une commande, encapsulez la commande dans $(et )- ou vous n'obtiendrez pas la sortie souhaitée.

Mez
la source
3
Cela ne fonctionnera pas lorsque j'appelle le script à partir du répertoire actuel.
Eric Wang
1
@EricWang vous êtes toujours dans le répertoire courant.
ctrl-alt-delor
Pour moi, $ current_dir est en effet le chemin à partir duquel j'appelle le script. Cependant, $ script_dir n'est pas le répertoire du script, c'est juste un point.
Michael
31

Si vous utilisez bash ....

#!/bin/bash

pushd $(dirname "${0}") > /dev/null
basedir=$(pwd -L)
# Use "pwd -P" for the path without links. man bash for more info.
popd > /dev/null

echo "${basedir}"
docwhat
la source
4
Vous pouvez remplacer le pushd/ popdpar cd $(dirname "${0}")et cd -le faire fonctionner sur d'autres shells, s'ils ont un pwd -L.
docwhat
pourquoi utiliseriez-vous pushd et popd ici?
qodeninja
1
Je n'ai donc pas à stocker le répertoire d'origine dans une variable. C'est un modèle que j'utilise beaucoup dans les fonctions et autres. Il niche très bien, ce qui est bien.
docwhat
Il est toujours stocké en mémoire - dans une variable - qu'une variable soit référencée dans votre script ou non. De plus, je pense que le coût d'exécution de pushd et popd l'emporte largement sur les économies de la non-création d'une variable Bash locale dans votre script, à la fois en termes de cycles CPU et de lisibilité.
ingyhere
20

Comme le suggère le Marko:

BASEDIR=$(dirname $0)
echo $BASEDIR

Cela fonctionne à moins que vous n'exécutiez le script à partir du même répertoire où le script réside, auquel cas vous obtenez une valeur de '.'

Pour contourner ce problème, utilisez:

current_dir=$(pwd)
script_dir=$(dirname $0)

if [ $script_dir = '.' ]
then
script_dir="$current_dir"
fi

Vous pouvez désormais utiliser la variable current_dir tout au long de votre script pour faire référence au répertoire de script. Cependant, cela peut toujours avoir le problème du lien symbolique.

ranamalo
la source
20

La meilleure réponse à cette question a été trouvée ici:
Obtenir le répertoire source d'un script Bash de l'intérieur

Et c'est:

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

One-liner qui vous donnera le nom de répertoire complet du script, peu importe d'où il est appelé.

Pour comprendre comment cela fonctionne, vous pouvez exécuter le script suivant:

#!/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" )" && pwd )"
if [ "$DIR" != "$RDIR" ]; then
  echo "DIR '$RDIR' resolves to '$DIR'"
fi
echo "DIR is '$DIR'"
Alexandro de Oliveira
la source
14
cd $(dirname $(readlink -f $0))
bleuté
la source
10

Faisons-en un oneliner POSIX:

a="/$0"; a=${a%/*}; a=${a#/}; a=${a:-.}; BASEDIR=$(cd "$a"; pwd)

Testé sur de nombreux shells compatibles Bourne, y compris ceux BSD.

Pour autant que je sache, je suis l'auteur et je l'ai mis dans le domaine public. Pour plus d'informations, voir: https://www.jasan.tk/posts/2017-05-11-posix_shell_dirname_replacement/

Ján Sáreník
la source
1
comme écrit, cd: too many argumentssi des espaces dans le chemin d'accès, et retourne $PWD. (une solution évidente, mais montre juste combien de cas de bord il y a réellement)
Michael
1
Souhaitez voter. Sauf pour le commentaire de @michael qu'il échoue avec des espaces dans le chemin ... Y a-t-il un correctif?
spechter
1
@spechter oui, il existe un correctif pour cela. Voir jasan.tk/posix/2017/05/11/posix_shell_dirname_replacement
Ján Sáreník
@Der_Meister - veuillez être plus précis. Ou écrivez (et éventuellement cryptez) un e-mail à jasan sur jasan.tk
Ján Sáreník
2
ln -s / home / der / 1 / test / home / der / 2 / test && / home / der / 2 / test => / home / der / 2 (devrait plutôt montrer le chemin vers le script original)
Der_Meister
10
BASE_DIR="$(cd "$(dirname "$0")"; pwd)";
echo "BASE_DIR => $BASE_DIR"
searchrome
la source
3
la manière la plus fiable non spécifique à bash que je connaisse.
eddygeek
10

Si vous souhaitez obtenir le répertoire de script réel (que vous appeliez le script à l'aide d'un lien symbolique ou directement), essayez:

BASEDIR=$(dirname $(realpath "$0"))
echo "$BASEDIR"

Cela fonctionne à la fois sur linux et macOS. Je ne pouvais voir personne parler ici realpath. Je ne sais pas s'il y a des inconvénients dans cette approche.

sur macOS, vous devez installer coreutilspour utiliser realpath. Par exemple: brew install coreutils.

Rohith
la source
6

INTRODUCTION

Cette réponse corrige la réponse très brisée mais choquante de ce fil (écrite par TheMarko):

#!/usr/bin/env bash

BASEDIR=$(dirname "$0")
echo "$BASEDIR"

POURQUOI L'UTILISATION de dirname "$ 0" SUR SON PROPRE NE FONCTIONNE PAS?

dirname $ 0 ne fonctionnera que si l'utilisateur lance le script d'une manière très spécifique. J'ai pu trouver plusieurs situations où cette réponse échoue et bloque le script.

Tout d'abord, comprenons comment cette réponse fonctionne. Il obtient le répertoire de script en faisant

dirname "$0"

$ 0 représente la première partie de la commande appelant le script (c'est essentiellement la commande entrée sans les arguments:

/some/path/./script argument1 argument2

$ 0 = "/ certains / chemin /./ script"

dirname trouve essentiellement le dernier / dans une chaîne et le tronque là. Donc si vous le faites:

  dirname /usr/bin/sha256sum

vous obtiendrez: / usr / bin

Cet exemple fonctionne bien car / usr / bin / sha256sum est un chemin correctement formaté mais

  dirname "/some/path/./script"

ne fonctionnerait pas bien et vous donnerait:

  BASENAME="/some/path/." #which would crash your script if you try to use it as a path

Disons que vous êtes dans le même répertoire que votre script et que vous le lancez avec cette commande

./script   

0 $ dans cette situation sera ./script et dirname $ 0 donnera:

. #or BASEDIR=".", again this will crash your script

En utilisant:

sh script

Sans entrer le chemin complet donnera également un BASEDIR = "."

Utilisation de répertoires relatifs:

 ../some/path/./script

Donne un nom de répertoire $ 0 de:

 ../some/path/.

Si vous êtes dans le répertoire / some et que vous appelez le script de cette manière (notez l'absence de / au début, encore une fois un chemin relatif):

 path/./script.sh

Vous obtiendrez cette valeur pour dirname $ 0:

 path/. 

et ./path/./script (une autre forme du chemin relatif) donne:

 ./path/.

Les deux seules situations où basedir $ 0 fonctionnera sont si l'utilisateur utilise sh ou touch pour lancer un script car les deux donneront $ 0:

 $0=/some/path/script

qui vous donnera un chemin que vous pouvez utiliser avec dirname.

LA SOLUTION

Vous devez prendre en compte et détecter chacune des situations mentionnées ci-dessus et appliquer un correctif si cela se produit:

#!/bin/bash
#this script will only work in bash, make sure it's installed on your system.

#set to false to not see all the echos
debug=true

if [ "$debug" = true ]; then echo "\$0=$0";fi


#The line below detect script's parent directory. $0 is the part of the launch command that doesn't contain the arguments
BASEDIR=$(dirname "$0") #3 situations will cause dirname $0 to fail: #situation1: user launches script while in script dir ( $0=./script)
                                                                     #situation2: different dir but ./ is used to launch script (ex. $0=/path_to/./script)
                                                                     #situation3: different dir but relative path used to launch script
if [ "$debug" = true ]; then echo 'BASEDIR=$(dirname "$0") gives: '"$BASEDIR";fi                                 

if [ "$BASEDIR" = "." ]; then BASEDIR="$(pwd)";fi # fix for situation1

_B2=${BASEDIR:$((${#BASEDIR}-2))}; B_=${BASEDIR::1}; B_2=${BASEDIR::2}; B_3=${BASEDIR::3} # <- bash only
if [ "$_B2" = "/." ]; then BASEDIR=${BASEDIR::$((${#BASEDIR}-1))};fi #fix for situation2 # <- bash only
if [ "$B_" != "/" ]; then  #fix for situation3 #<- bash only
        if [ "$B_2" = "./" ]; then
                #covers ./relative_path/(./)script
                if [ "$(pwd)" != "/" ]; then BASEDIR="$(pwd)/${BASEDIR:2}"; else BASEDIR="/${BASEDIR:2}";fi
        else
                #covers relative_path/(./)script and ../relative_path/(./)script, using ../relative_path fails if current path is a symbolic link
                if [ "$(pwd)" != "/" ]; then BASEDIR="$(pwd)/$BASEDIR"; else BASEDIR="/$BASEDIR";fi
        fi
fi

if [ "$debug" = true ]; then echo "fixed BASEDIR=$BASEDIR";fi
thebunnyrules
la source
4

Ce one-liner indique où se trouve le script shell, peu importe si vous l'avez exécuté ou si vous l'avez obtenu . En outre, il résout tous les liens symboliques impliqués, si tel est le cas:

dir=$(dirname $(test -L "$BASH_SOURCE" && readlink -f "$BASH_SOURCE" || echo "$BASH_SOURCE"))

Au fait, je suppose que vous utilisez / bin / bash .

Richard Gomes
la source
2

Autant de réponses, toutes plausibles, chacune avec des avantages et des inconvénients et des objectifs légèrement différents (qui devraient probablement être énoncés pour chacun). Voici une autre solution qui répond à un objectif principal à la fois d'être clair et de fonctionner sur tous les systèmes, sur tous les bash (pas d'hypothèses sur les versions de bash readlinkou les pwdoptions), et fait raisonnablement ce que vous attendez (par exemple, la résolution de liens symboliques est un problème intéressant, mais ce n'est généralement pas ce que vous voulez réellement), gérez les cas marginaux comme les espaces dans les chemins, etc., ignore les erreurs et utilise une valeur par défaut saine s'il y a des problèmes.

Chaque composant est stocké dans une variable distincte que vous pouvez utiliser individuellement:

# script path, filename, directory
PROG_PATH=${BASH_SOURCE[0]}      # this script's name
PROG_NAME=${PROG_PATH##*/}       # basename of script (strip path)
PROG_DIR="$(cd "$(dirname "${PROG_PATH:-$PWD}")" 2>/dev/null 1>&2 && pwd)"
Michael
la source
-4

Cela devrait faire l'affaire:

echo `pwd`/`dirname $0`

Cela peut sembler laid selon la façon dont il a été invoqué et le cwd, mais devrait vous amener là où vous devez aller (ou vous pouvez modifier la chaîne si vous vous souciez de son apparence).

kenorb
la source
1
problème d'échappement stackoverflow ici: il devrait sûrement ressembler à ceci: `pwd`/`dirname $0`mais peut toujours échouer sur les liens symboliques
Andreas Dietrich