Obtener el directorio fuente de un script Bash desde dentro

¿Cómo obtengo la ruta del directorio en el que se encuentra un script de Bash , dentro de ese script?

Por ejemplo, digamos que quiero usar un script Bash como iniciador para otra aplicación. Quiero cambiar el directorio de trabajo al que está ubicado el script Bash, así puedo operar los archivos en ese directorio, así:

  $ ./application 

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

es un útil delineador de líneas que le dará el nombre completo del directorio del script, sin importar desde dónde se lo llame.

Funcionará siempre que el último componente de la ruta utilizada para encontrar el script no sea un enlace simbólico (los enlaces de directorio son correctos). Si también desea resolver cualquier enlace al script en sí, necesita una solución de varias líneas:

 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 && 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 && pwd )" 

Este último funcionará con cualquier combinación de alias, source , bash -c , enlaces simbólicos, etc.

Tenga cuidado: si realiza un cd a un directorio diferente antes de ejecutar este fragmento, ¡el resultado puede ser incorrecto! Además, ten cuidado con $CDPATH .

Para entender cómo funciona, intente ejecutar esta forma más detallada:

 #!/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'" 

E imprimirá algo como:

 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' 

Use dirname "$0" :

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

El uso de pwd sí solo no funcionará si no está ejecutando el script desde el directorio en el que está contenido.

 [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 

El comando dirname es el más básico, simplemente analiza la ruta hasta el nombre de archivo de la variable $ 0 (nombre del script):

 dirname "$0" 

Pero, como señaló matt b , la ruta devuelta es diferente dependiendo de cómo se llame al script. pwd no hace el trabajo porque eso solo le dice cuál es el directorio actual, no en qué directorio reside el script. Además, si se ejecuta un enlace simbólico a un script, obtendrá una ruta (probablemente relativa) donde reside el enlace, no el guión real.

Algunos otros han mencionado el comando readlink , pero en su forma más simple, puede usar:

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

readlink resolverá la ruta del script a una ruta absoluta desde la raíz del sistema de archivos. Por lo tanto, cualquier ruta que contenga puntos simples o dobles, tildes y / o enlaces simbólicos se resolverá en una ruta completa.

Aquí hay un script que demuestra cada uno de estos, 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))" 

Ejecutando esta secuencia de comandos en mi directorio de inicio, usando una ruta relativa:

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

De nuevo, pero usando la ruta completa al script:

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

Ahora cambiando directorios:

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

Y finalmente usando un enlace simbólico para ejecutar el script:

 >>>$ ln -s ~/whatdir.sh whatdirlink.sh >>>$ ./whatdirlink.sh pwd: /tmp $0: ./whatdirlink.sh basename: whatdirlink.sh dirname: . dirname/readlink: /Users/phatblat 
 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 

Funciona para todas las versiones, incluyendo

  • cuando se llama a través del enlace suave de profundidad multple,
  • cuando el archivo
  • cuando script llamado por comando ” source ” aka . (punto) operador.
  • cuando arg $0 se modifica de la persona que llama.
  • "./script"
  • "/full/path/to/script"
  • "/some/path/../../another/path/script"
  • "./some/folder/script"

Alternativamente, si el script bash en sí es un enlace simbólico relativo , desea seguirlo y devolver la ruta completa del script vinculado al:

 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_PATH se da en la ruta completa, sin importar cómo se llame.
Solo asegúrate de ubicar esto al inicio del script.

Este comentario y código Copyleft, licencia seleccionable bajo GPL2.0 o posterior o CC-SA 3.0 (CreativeCommons Share Alike) o posterior. (c) 2008. Todos los derechos reservados. Sin garantía de ningún tipo. Usted ha sido advertido.
http://www.gnu.org/licenses/gpl-2.0.txt
http://creativecommons.org/licenses/by-sa/3.0/
18eedfe1c99df68dc94d4a94712a71aaa8e1e9e36cacf421b9463dd2bbaa02906d0d6656

Respuesta corta:

 `dirname $0` 

o ( preferiblemente ):

 $(dirname "$0") 

Puede usar $ BASH_SOURCE

 #!/bin/bash scriptdir=`dirname "$BASH_SOURCE"` 

Tenga en cuenta que necesita usar #! / Bin / bash y no #! / Bin / sh ya que es una extensión de bash

Esto debería hacerlo:

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

Funciona con enlaces simbólicos y espacios en el camino. Consulte las páginas man para dirname y readlink .

Editar:

De la pista de comentarios parece no funcionar con Mac OS. No tengo idea de por qué es eso. ¿Alguna sugerencia?

pwd se puede usar para encontrar el directorio de trabajo actual, y dirname para encontrar el directorio de un archivo en particular (el comando que se ejecutó, es $0 , por lo que dirname $0 debería darle el directorio de la secuencia de comandos actual).

Sin embargo, dirname proporciona precisamente la parte de directorio del nombre de archivo, que probablemente sea relativa al directorio de trabajo actual. Si su script necesita cambiar el directorio por alguna razón, entonces el resultado de dirname sentido.

Sugiero lo siguiente:

 #!/bin/bash reldir=`dirname $0` cd $reldir directory=`pwd` echo "Directory is $directory" 

De esta manera, obtienes un directorio absoluto, en lugar de relativo.

Como la secuencia de comandos se ejecutará en una instancia de bash separada, no es necesario restaurar el directorio de trabajo posteriormente, pero si desea volver a cambiar en la secuencia de comandos por alguna razón, puede asignar fácilmente el valor de pwd a una variable antes cambia de directorio, para uso futuro.

Aunque solo

 cd `dirname $0` 

resuelve el escenario específico en la pregunta, me parece que tiene el camino absoluto para ser más útil en general.

No creo que esto sea tan fácil como otros lo han hecho. pwd no funciona, ya que el directorio actual no es necesariamente el directorio con el script. $ 0 no siempre tiene la información tampoco. Considere las siguientes tres formas de invocar un script.

 ./script /usr/bin/script script 

De la primera y la tercera forma, $ 0 no tiene la información de ruta completa. En el segundo y el tercero, pwd no funciona. La única forma de obtener el directorio de la tercera manera sería ejecutar la ruta y encontrar el archivo con la coincidencia correcta. Básicamente, el código tendría que volver a hacer lo que hace el sistema operativo.

Una forma de hacer lo que está pidiendo sería simplemente codificar los datos en el directorio / usr / share y hacer referencia a él por la ruta completa. Los datos no deberían estar en el directorio / usr / bin de todos modos, así que probablemente esto sea lo que hay que hacer.

 SCRIPT_DIR=$( cd ${0%/*} && pwd -P ) 

Esto obtiene el directorio de trabajo actual en Mac OS X 10.6.6:

 DIR=$(cd "$(dirname "$0")"; pwd) 

Esto es específico de Linux, pero podrías usar:

 SELF=$(readlink /proc/$$/fd/255) 
 $(dirname "$(readlink -f "$BASH_SOURCE")") 

Aquí hay un trazador de líneas que cumple con POSIX:

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

Intenté cada uno de estos y ninguno de ellos funcionó. Uno estaba muy cerca, pero tenía un pequeño error que lo rompió mal; se olvidaron de envolver el camino entre comillas.

Además, muchas personas suponen que estás ejecutando el script desde un shell, así que olvídate de abrir un nuevo script que se convierta en tu hogar por defecto.

Pruebe este directorio por tamaño:

/ var / Nadie / Pensamiento / Acerca de los espacios Ser / En un directorio / Nombre / Y aquí está su archivo de texto

Esto lo hace bien independientemente de cómo o dónde lo ejecutes.

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

Entonces, para hacerlo realmente útil, veamos cómo cambiar al directorio del script en ejecución:

 cd "`dirname "$0"`" 

Espero que ayude

Yo usaría algo como esto:

 # 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 

Una pequeña revisión de la solución e-satis y 3bcdnlklvc04a señaló en su respuesta

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

Esto debería funcionar en todos los casos que enumeraron.

EDITAR: evita popd después de pushd fallido, gracias a konsolebox

Aquí está la manera simple y correcta:

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

Explicación:

  • ${BASH_SOURCE[0]} – la ruta completa al script. El valor de esto será correcto incluso cuando el script se esté obteniendo, p. Ej. source <(echo 'echo $0') imprime bash , mientras que si lo reemplaza con ${BASH_SOURCE[0]} imprimirá la ruta completa del script. (Por supuesto, esto supone que estás bien tomando una dependencia de Bash).

  • readlink -f - Recursivamente resuelve cualquier enlace simbólico en la ruta especificada. Esta es una extensión de GNU, y no está disponible en (por ejemplo) sistemas BSD. Si está ejecutando una Mac, puede usar Homebrew para instalar GNU coreutils y suplantar esto con greadlink -f .

  • Y, por supuesto, dirname obtiene el directorio padre de la ruta.

 #!/bin/sh PRG="$0" # need this for relative symlinks while [ -h "$PRG" ] ; do PRG=`readlink "$PRG"` done scriptdir=`dirname "$PRG"` 

$ _ vale la pena mencionar como una alternativa a $ 0. Si está ejecutando un script desde bash, la respuesta aceptada puede acortarse a:

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

Tenga en cuenta que esta tiene que ser la primera statement en su secuencia de comandos.

He comparado muchas de las respuestas dadas y propongo algunas soluciones más compactas. Estos parecen manejar todos los casos extremos que surgen de su combinación favorita de:

  • Rutas absolutas o caminos relativos
  • Enlaces blandos de archivos y directorios
  • Invocación como script , script bash script , bash script bash -c script , source script o . script . script
  • Espacios, tabs, nuevas líneas, unicode, etc. en directorios y / o nombre de archivo
  • Nombres de archivos que comienzan con un guión

Si está ejecutando desde Linux, parece que usar el manejador de proceso es la mejor solución para ubicar la fuente totalmente resuelta del script actualmente en ejecución (en una sesión interactiva, el enlace apunta al respectivo /dev/pts/X ):

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

Esto tiene un poco de fealdad, pero la solución es compacta y fácil de entender. No estamos utilizando bash primitives solamente, pero estoy de acuerdo con eso porque readlink simplifica la tarea considerablemente. El echo X agrega una X al final de la cadena de variables para que no se consum ningún espacio en blanco al final del nombre de archivo, y la sustitución de los parámetros ${VAR%X} al final de la línea se deshace de la X Debido a que readlink agrega una nueva línea propia (que normalmente se comería en la sustitución de comandos si no fuera por nuestro truco anterior), tenemos que deshacernos de eso también. Esto se logra más fácilmente usando el esquema de cotización $'' , que nos permite usar secuencias de escape como \n para representar nuevas líneas (esta es también la forma en que puede crear fácilmente directorios y archivos desviados).

Lo anterior debe cubrir sus necesidades para ubicar el script actualmente en ejecución en Linux, pero si no tiene el sistema de archivos proc a su disposición, o si está tratando de localizar la ruta completamente resuelta de algún otro archivo, entonces tal vez ‘ Encontraré el siguiente código útil. Es solo una pequeña modificación del anterior delineador. Si está jugando con directorios / nombres de archivos extraños, comprobar el resultado con ls y readlink es informativo, ya que ls generará rutas “simplificadas”, sustituyendo ? para cosas como nuevas líneas.

 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" 

Para sistemas que tienen GNU coreutils readlink (por ejemplo, linux):

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

No es necesario usar BASH_SOURCE cuando $0 contiene el nombre de archivo del script.

Intenta usar:

 real=$(realpath $(dirname $0)) 

Entonces … creo que tengo este. Tarde en la fiesta, pero creo que algunos apreciarán que esté aquí si encuentran este hilo. Los comentarios deberían explicar.

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

Hmm, si en el camino basename y dirname simplemente no van a cortarlo y caminar, el camino es difícil (¿qué pasa si el padre no exportó PATH!). Sin embargo, el shell debe tener un control abierto para su script, y en bash, el identificador es # 255.

 SELF=`readlink /proc/$$/fd/255` 

funciona para mi.

Pruebe la siguiente solución compatible cruzada:

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

ya que los comandos realpath o readlink no están siempre disponibles (dependiendo del sistema operativo) y ${BASH_SOURCE[0]} está disponible solo en bash shell.

Alternativamente, puedes probar la siguiente función en bash:

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

Esta función toma 1 argumento. Si el argumento ya tiene una ruta absoluta, imprímalo tal como está, de lo contrario imprima $PWD variable + argumento de nombre de archivo (sin ./ prefijo).

Relacionado:

  • ¿Cómo configurar el directorio de trabajo actual en el directorio del script?
  • Ruta absoluta del script Bash con OSX
  • ¿Manera confiable para que un script bash obtenga el camino completo a sí mismo?

Esto funciona en bash-3.2:

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

Aquí hay un ejemplo de su uso:

Supongamos que tiene un directorio ~ / bin , que está en su $ PATH . Usted tiene el script A dentro de este directorio. Es script s fuente ~ / bin / lib / B. Usted sabe dónde está el script incluido en relación con el original (el subdirectorio lib ), pero no en relación con el directorio actual del usuario.

Esto se resuelve mediante lo siguiente (dentro de A ):

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

No importa dónde esté el usuario o cómo llame al script, esto siempre funcionará.

This is the only way I’ve found to tell reliably:

 SCRIPT_DIR=$(dirname $(cd "$(dirname "$BASH_SOURCE")"; pwd)) 

None of these worked for a bash script launched by Finder in OS X – I ended up using:

 SCRIPT_LOC="`ps -p $$ | sed /PID/d | sed s:.*/Network/:/Network/: | sed s:.*/Volumes/:/Volumes/:`" 

Not pretty, but it gets the job done.

The best compact solution in my view would be:

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

There is no reliance on anything other than Bash. The use of dirname , readlink and basename will eventually lead to compatibility issues, so they are best avoided if at all possible.