web-dev-qa-db-fra.com

Unix Shell script recherche dans quel répertoire se trouve le fichier de script?

Fondamentalement, je dois exécuter le script avec des chemins 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ù se trouve le fichier de script?

440
William Yeung

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

#!/usr/bin/env bash

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

Le message original 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 readlink avec l’option -f. Fonctionne lorsque le script est appelé par un absolu aussi bien 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

363
al.

Un commentaire précédent sur une réponse l'a dit, mais il est facile de rater toutes les autres réponses.

Lorsque vous utilisez bash:

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

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

48
Daniel

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 il se trouve. 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 à partir du résultat d'une commande, enveloppez-la dans $( et ) - ou vous n'obtiendrez pas le résultat souhaité.

38
Mez

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}"
31
docwhat

Comme le suggère le Marko:

BASEDIR=$(dirname $0)
echo $BASEDIR

Cela ne fonctionne que si vous exécutez le script à partir du même répertoire que celui-ci, auquel cas vous obtenez la valeur '.'

Pour résoudre ce problème, utilisez:

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

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

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

18
ranamalo

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

Et 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'"
17
cd $(dirname $(readlink -f $0))
14
blueyed

Faisons-en un oneliner POSIX:

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

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

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

8
Ján Sáreník
BASE_DIR="$(cd "$(dirname "$0")"; pwd)";
echo "BASE_DIR => $BASE_DIR"
8
searchrome

INTRODUCTION

Cette réponse corrige la réponse très brisée mais choquante du vote supérieur de ce fil (écrit par TheMarko):

#!/usr/bin/env bash

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

POURQUOI UTILISER 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"

$ représente la première partie de la commande appelant le script (il s’agit essentiellement de la commande entrée sans les arguments:

/some/path/./script argument1 argument2

$ 0 = "/ some/path /./ script"

dirname trouve fondamentalement le dernier/dans une chaîne et le tronque à cet endroit. 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

Dites 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 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 dans lesquelles basedir $ 0 fonctionnera si l'utilisateur utilise sh ou touch pour lancer un script, car les deux résultent en $ 0:

 $0=/some/path/script

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

LA SOLUTION

Vous auriez un compte et détecteriez chacune des situations mentionnées ci-dessus et appliqueriez une solution si elle se présentait:

#!/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
4
thebunnyrules

Si vous voulez obtenir le répertoire de script réel (que vous appeliez le script via un lien symbolique ou directement), essayez:

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

Cela fonctionne à la fois sous linux et macOS. Je ne voyais personne ici mentionner realpath. Je ne sais pas s'il y a des inconvénients à cette approche.

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

3
Rohith

Ce one-liner indique où se trouve le script Shell, peu importe si vous l'avez exécuté ou s'il vous a été attribué. 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"))

En passant, je suppose que vous utilisez / bin/bash.

3
Richard Gomes

Autant de réponses, toutes plausibles, chacune avec des objectifs favorables et défavorables (qui devraient probablement être énoncés pour chacune). Voici une autre solution qui répond à un objectif primordial: être clair et fonctionner sur tous les systèmes, sur tous les systèmes bash (pas d’hypothèses sur les versions bash, ni sur les options readlink ou pwd), et fait raisonnablement ce que vous feriez. Attendez-vous à ce que cela se produise (par exemple, la résolution des liens symboliques est un problème intéressant, mais qui n’est pas ce que vous voulez réellement), gérez les cas Edge comme les espaces dans les chemins, etc., ignore les erreurs et utilise une valeur par défaut si nécessaire.

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)"
2
michael

Inspiré par la réponse de blueyed

read < <(readlink -f $0 | xargs dirname)
cd $REPLY
2
Steven Penny