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:
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.
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 ...
La solution readlink
sur 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:
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 ).
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`
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>
.
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.
Il n'utilise pas l'option -f
dans readlink et devrait donc fonctionner sous BSD/Mac OS X.
.
)foo->dir1/dir2/bar bar->./../doe doe->script
Je cherche des cas où ce code ne fonctionne pas. S'il vous plaît, faites-moi savoir.
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`]"
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.)
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.
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 .
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))
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
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.
Simplement:
BASEDIR=$(readlink -f $0 | xargs dirname)
Les opérateurs de fantaisie ne sont pas nécessaires.
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:
Bourne Shell (sh
) de manière conforme:
SCRIPT_HOME=`dirname $0 | while read a; do cd $a && pwd && break; done`
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?
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)
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.
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.
Essaye ça:
cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0))
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/"
Bon mot
`dirname $(realpath $0)`
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