Cómo obtener el directorio de origen de un script Bash desde el propio script

4956

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

Quiero usar un script Bash como lanzador para otra aplicación. Quiero cambiar el directorio de trabajo al que está ubicado el script Bash, para poder operar en los archivos de ese directorio, así:

$ ./application
Jiaaro
fuente
69
Ninguna de las soluciones actuales funciona si hay líneas nuevas al final del nombre del directorio : serán reemplazadas por la sustitución del comando. Para evitar esto, puede agregar un carácter que no sea de nueva línea dentro de la sustitución de comando DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd && echo x)"- y eliminarlo sin una sustitución de comando - DIR="${DIR%x}".
l0b0
80
@ jpmc26 Hay dos situaciones muy comunes: accidentes y sabotaje. Un script no debería fallar de manera impredecible solo porque alguien, en algún lugar, hizo un mkdir $'\n'.
l0b0
24
cualquiera que permita que las personas saboteen su sistema de esa manera no debería dejarlo en paz para detectar tales problemas ... y mucho menos contratar personas capaces de cometer ese tipo de error. Nunca he visto, en los 25 años de uso de bash, visto este tipo de cosas suceder en cualquier lugar ... es por eso que tenemos cosas como perl y prácticas como el control de manchas (probablemente me sentiré culpable por decir eso :)
osirisgothra
3
@ l0b0 Tenga en cuenta que necesitaría la misma protección dirnamey que el directorio podría comenzar con un -(por ejemplo --help). DIR=$(reldir=$(dirname -- "$0"; echo x); reldir=${reldir%?x}; cd -- "$reldir" && pwd && echo x); DIR=${DIR%?x}. Tal vez esto es exagerado?
Score_Under
56
Recomiendo leer estas preguntas frecuentes de Bash sobre el tema.
Rany Albeg Wein

Respuestas:

6575
#!/bin/bash

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

es una línea útil que le dará el nombre completo del directorio de la secuencia de comandos sin importar de dónde se 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 están bien). Si también desea resolver cualquier enlace al script en sí, necesita una solución de varias líneas:

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

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

Cuidado: si va cda un directorio diferente antes de ejecutar este fragmento, ¡el resultado puede ser incorrecto!

Además, tenga cuidado con las $CDPATHtrampas y los efectos secundarios de salida de stderr si el usuario ha anulado de manera inteligente el cd para redirigir la salida a stderr en su lugar (incluidas las secuencias de escape, como cuando se llama update_terminal_cwd >&2en Mac). Agregar >/dev/null 2>&1al final de su cdcomando se encargará de ambas posibilidades.

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" )" >/dev/null 2>&1 && 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'
Dave Dopson
fuente
27
Puede fusionar este enfoque con la respuesta por user25866 para llegar a una solución que funciona con source <script>y bash <script>: DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)".
Dan Moulding
21
¡A veces cdimprime algo en STDOUT! Por ejemplo, si su $CDPATHcuenta .. Para cubrir este caso, useDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"
user716468
182
Esta respuesta aceptada no está bien, no funciona con enlaces simbólicos y es demasiado compleja. dirname $(readlink -f $0)es el comando correcto Ver gist.github.com/tvlooy/cbfbdb111a4ebad8b93e para un caso de prueba
tvlooy
167
@tvlooy IMO su respuesta tampoco es exactamente correcta tal cual, porque falla cuando hay un espacio en el camino. A diferencia de un personaje de nueva línea, esto no es improbable o incluso poco común. dirname "$(readlink -f "$0")"no agrega complejidad y es una medida justa más robusta para la mínima cantidad de problemas.
Adrian Günter
11
@tvlooy su comentario no es compatible con macOS (o probablemente BSD en general), mientras que la respuesta aceptada es. readlink -f $0da readlink: illegal option -- f.
Alexander Ljungberg
876

Uso dirname "$0":

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

usar pwdsolo 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
mate b
fuente
25
Para la portabilidad más allá de bash, $ 0 puede no ser siempre suficiente. Es posible que deba sustituir "type -p $ 0" para que esto funcione si se encontró el comando en la ruta.
Darron el
10
@Darron: solo puedes usarlo type -psi el script es ejecutable. Esto también puede abrir un agujero sutil si el script se ejecuta usando bash test2.shy hay otro script con el mismo nombre ejecutable en otro lugar.
D.Shawley
90
@Darron: pero como la pregunta está etiquetada bashy la línea hash-bang menciona explícitamente /bin/bash, diría que es bastante seguro depender de los bashismos.
Joachim Sauer
34
+1, pero el problema con el uso dirname $0es que si el directorio es el directorio actual, obtendrá .. Eso está bien a menos que vayas a cambiar los directorios en el script y esperes usar la ruta que obtuviste dirname $0como si fuera absoluta. Para obtener la ruta absoluta: pushd `dirname $0` > /dev/null, SCRIPTPATH=`pwd`, popd > /dev/null: pastie.org/1489386 (Pero sin duda hay una mejor manera de ampliar ese camino?)
TJ Crowder
99
@TJ Crowder No estoy seguro de que dirname $0haya un problema si lo asigna a una variable y luego lo usa para iniciar un script como $dir/script.sh; Me imagino que este es el caso de uso para este tipo de cosas el 90% del tiempo. ./script.shfuncionaría bien
matt b
516

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

dirname "$0"

Pero, como señaló Matt B , el camino devuelto es diferente dependiendo de cómo se llame el script. pwdno 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) a donde reside el enlace, no el script real.

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

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

readlinkresolverá 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 este script 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
phatblat
fuente
13
readlinkno estará disponible en alguna plataforma en la instalación predeterminada. Intenta evitar usarlo si puedes
TL
43
tenga cuidado de citar todo para evitar problemas de espacios en blanco:export SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
Catskul
13
En OSX, Yosemite 10.10.1 -fno se reconoce como una opción para readlink. Usar en su stat -flugar hace el trabajo. Gracias
cucu8
11
En OSX, la hay greadlink, que es básicamente la readlinkque todos conocemos. Aquí hay una versión independiente de la plataforma:dir=`greadlink -f ${BASH_SOURCE[0]} || readlink -f ${BASH_SOURCE[0]}`
Robert
66
Buena llamada, @robert. Para su información, greadlinkse puede instalar fácilmente a través de 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

Funciona para todas las versiones, incluidas

  • cuando se llama a través de un enlace suave de profundidad múltiple,
  • cuando el archivo
  • cuando la secuencia de comandos se llama mediante el comando " source" .operador aka (punto).
  • cuando arg $0se 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:

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_PATHse da en ruta completa, no importa cómo se llame.
Solo asegúrate de localizar esto al comienzo del script.

Este comentario y código Copyleft, licencia seleccionable bajo la 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

usuario25866
fuente
44
¡Agradable! Podría hacerse más corto reemplazando "pushd [...] popd / dev / null" por SCRIPT_PATH = readlink -f $(dirname "${VIRTUAL_ENV}");
e-satis
55
Y en lugar de usar pushd ...; ¿no sería mejor usar $ (cd dirname "${SCRIPT_PATH}"&& pwd)? Pero de todos modos gran guión!
ovanes
66
Es peligroso que una secuencia de comandos cdsalga de su directorio actual con la esperanza de cdvolver a aparecer más tarde: la secuencia de comandos puede no tener permiso para volver a cambiar el directorio actual al que se invocó. (Lo mismo ocurre con pushd / popd)
Adrian Pronk
77
readlink -fes específico de GNU. BSD readlinkno tiene esa opción.
Kara Brightwell
3
¿Qué pasa con todas las subcapas innecesarias? ([ ... ])es menos eficiente que [ ... ], y no se aprovecha el aislamiento ofrecido a cambio de ese rendimiento alcanzado aquí.
Charles Duffy
110

Respuesta corta:

`dirname $0`

o ( preferiblemente ):

$(dirname "$0")
Fabien
fuente
17
No funcionará si obtiene el script. "source my / script.sh"
Arunprasad Rajkumar
Lo uso todo el tiempo en mis scripts de bash que automatizan cosas y a menudo invocan otros scripts en el mismo directorio. Nunca los usaría sourcey cd $(dirname $0)es fácil de recordar.
kqw
16
@vidstige: en ${BASH_SOURCE[0]}lugar de $0trabajar consource my/script.sh
Timothy Jones
@TimothyJones que fallará el 100% del tiempo si proviene de cualquier otro shell que no sea bash. ${BASH_SOURCE[0]}no es satisfactorio en absoluto ${BASH_SOURCE:-0}es mucho mejor.
Mathieu CAROFF
106

Puedes usar $BASH_SOURCE:

#!/bin/bash

scriptdir=`dirname "$BASH_SOURCE"`

Tenga en cuenta que debe usar #!/bin/bashy no, #!/bin/shya que es una extensión Bash.

Mr Shark
fuente
14
Cuando lo hago ./foo/script, entonces lo $(dirname $BASH_SOURCE)es ./foo.
Hasta el
1
@ Hasta, en este caso, podemos usar el realpathcomando para obtener la ruta completa de ./foo/script. Así dirname $(realpath ./foo/script) dará el camino del guión.
purushothaman poovai
73

Esto debería hacerlo:

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

Esto funciona con enlaces simbólicos y espacios en la ruta.

Vea las páginas de manual para dirnamey readlink.

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

Simon Rigét
fuente
66
con su solución, invocando el script como ./script.shmuestra en .lugar de la ruta completa del directorio
Bruno Negrão Zica
55
No hay opción -f para readlink en MacOS. Usar en su statlugar. Pero aún así, muestra .si estás en 'este' directorio.
Denis The Menace
2
Debe instalarlo coreutilsdesde Homebrew y usarlo greadlinkpara obtener la -fopción en MacOS porque es * BSD debajo de las cubiertas y no Linux.
dragon788
Debe agregar comillas dobles alrededor del lado derecho:DIR="$(dirname "$(readlink -f "$0")")"
hagello
60

pwdse puede usar para encontrar el directorio de trabajo actual y dirnamepara encontrar el directorio de un archivo en particular (el comando que se ejecutó es $0, por dirname $0lo tanto, debería darle el directorio del script actual).

Sin embargo, dirnameproporciona con precisión la parte del directorio del nombre de archivo, que probablemente será relativa al directorio de trabajo actual. Si su script necesita cambiar de directorio por alguna razón, entonces la salida de no tiene dirnamesentido.

Sugiero lo siguiente:

#!/bin/bash

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

echo "Directory is $directory"

De esta manera, obtienes un directorio absoluto, más que relativo.

Dado que el script se ejecutará en una instancia de bash separada, no es necesario restaurar el directorio de trabajo después, pero si desea volver a cambiar su script por alguna razón, puede asignar fácilmente el valor de pwduna variable antes de cambiar directorio, para uso futuro.

Aunque solo

cd `dirname $0`

resuelve el escenario específico en la pregunta, creo que tener el camino absoluto a más más generalmente útil.

SpoonMeiser
fuente
99
Puede hacerlo todo en una línea como esta: DIRECTORIO = $ (cd dirname $0&& pwd)
dogbane
Esto no funciona si el script genera otro script y desea saber el nombre de este último.
reinierpost
52

Estoy cansado de ir a esta página una y otra vez para copiar y pegar la línea en la respuesta aceptada. El problema con eso es que no es fácil de entender y recordar.

Aquí hay un script fácil de recordar:

DIR="$(dirname "${BASH_SOURCE[0]}")"  # get the directory name
DIR="$(realpath "${DIR}")"    # resolve its full path if need be
Thamme Gowda
fuente
2
O, más oscuramente, en una línea: DIR=$(realpath "$(dirname "${BASH_SOURCE[0]}")")
agc
¿Por qué no es esta la respuesta aceptada? ¿Hay alguna diferencia con el uso realpathde resolver "manualmente" con un bucle de readlink? Incluso la readlinkpágina del manual diceNote realpath(1) is the preferred command to use for canonicalization functionality.
User9123
1
Y por cierto, ¿no deberíamos aplicar realpathantes dirname, no después? Si el archivo de script en sí es un enlace simbólico ... Daría algo así DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")". En realidad muy cerca de la respuesta propuesta por Simon.
Usuario9123
@ User9123 Creo que aceptar es intentar ser compatible con todos los populares shell / distro. Además, dependiendo de lo que intente hacer, en la mayoría de los casos la gente quiere obtener el directorio donde se encuentra el enlace simbólico en lugar del directorio de la fuente real.
Wang
37

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

./script

/usr/bin/script

script

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

Una forma de hacer lo que está pidiendo sería simplemente codificar los datos en el /usr/sharedirectorio y hacer referencia a ellos por su ruta completa. Los datos no deben estar en el /usr/bindirectorio de todos modos, por lo que probablemente sea lo que hay que hacer.

Jim
fuente
99
Si tiene la intención de refutar su comentario, PROBAR que un script PUEDE acceder a donde está almacenado con un código de ejemplo.
Richard Duerr
34
SCRIPT_DIR=$( cd ${0%/*} && pwd -P )
PM
fuente
Esto es mucho más corto que la respuesta elegida. Y parece funcionar igual de bien. Esto merece 1000 votos solo para que la gente no lo pase por alto.
Patrick
2
Como muchas de las respuestas anteriores explican en detalle, $0ni pwdse garantiza que tengan la información correcta, dependiendo de cómo se invoque el script.
IMSoP
34
$(dirname "$(readlink -f "$BASH_SOURCE")")
prueba11
fuente
Prefiero, $BASH_SOURCEmás $0, porque es explícito incluso para lectores que no están bien versados ​​en bash. $(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
blobmaster
32

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

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

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

SELF=$(readlink /proc/$$/fd/255)
Steve Baker
fuente
1
También es específico de bash, pero ¿quizás ha cambiado el comportamiento de bash? /proc/fd/$$/255parece apuntar a tty, no a un directorio. Por ejemplo, en mi shell de inicio de sesión actual, todos los descriptores de archivo 0, 1, 2 y 255 hacen referencia /dev/pts/4. En cualquier caso, el manual de bash no menciona fd 255, por lo que probablemente no sea prudente depender de este comportamiento. \
Keith Thompson
2
Shell interactivo! = Script. De todos modos realpath ${BASH_SOURCE[0]};, parece ser el mejor camino a seguir.
Steve Baker,
23

Aquí hay un one-liner compatible con POSIX:

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

# test
echo $SCRIPT_PATH
lamawithonel
fuente
44
Tuve éxito con esto cuando ejecuté un script solo o usando sudo, pero no cuando llamé a source ./script.sh
Michael R
Y falla cuando cdse configura para imprimir el nuevo nombre de ruta.
Aaron Digulla el
18

Intenté todo esto y ninguno funcionó. Uno estaba muy cerca pero tenía un pequeño error que lo rompió gravemente; se olvidaron de envolver el camino entre comillas.

Además, muchas personas suponen que está ejecutando el script desde un shell, por lo que se olvidan que cuando abre un nuevo script, el valor predeterminado es su hogar.

Pruebe este directorio para ver el tamaño:

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

Esto lo hace bien independientemente de cómo o dónde lo ejecute:

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

Entonces, para que sea realmente útil, aquí le mostramos cómo cambiar al directorio del script en ejecución:

cd "`dirname "$0"`"
Mike Bethany
fuente
44
No funciona si el script se obtiene de otro script.
reinierpost
Esto no funciona si la última parte de $ 0 es un enlace simbólico que apunta a una entrada de otro directorio ( ln -s ../bin64/foo /usr/bin/foo).
hagello
17

Aquí está la forma simple y correcta:

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

Explicación:

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

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

  • Y, por supuesto, dirnameobtiene el directorio principal de la ruta.

James Ko
fuente
1
greadlink -fdesafortunadamente no funciona de manera efectiva cuando se sourceejecuta el script en Mac :(
Gabe Kopley
17

La forma más corta y elegante de hacer esto es:

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

Esto funcionaría en todas las plataformas y es súper limpio.

Se pueden encontrar más detalles en " ¿En qué directorio está ese script bash? ".

Atul
fuente
gran solución limpia, pero esto no funcionará si el archivo está vinculado.
Ruuter
16

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
Nicolas
fuente
Este es el verdadero! Funciona con simple shtambién! Problema con dirname "$0"soluciones simples : si el script está en $PATHy se invoca sin ruta, dará un resultado incorrecto.
Notinlist
@Notinlist No es así. Si el script se encuentra a través de PATH, $0contendrá el nombre de archivo absoluto. Si la secuencia de comandos se invoca con un nombre de archivo relativo o absoluto que contiene un /, $0lo contendrá.
Neil Mayhew
No funcionará para un script de origen.
Amit Naidu
16

Esta es una ligera revisión de la solución e-satis y 3bcdnlklvc04a señalaron en su respuesta :

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

Esto aún debería funcionar en todos los casos enumerados.

Esto evitará popddespués de un pushderror, gracias a konsolebox.

Fuwjax
fuente
Esto funciona perfectamente para obtener el nombre de directorio "real", en lugar de solo el nombre de un enlace simbólico. ¡Gracias!
Jay Taylor
1
MejorSCRIPT_DIR=''; pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && { SCRIPT_DIR=$PWD; popd > /dev/null; }
konsolebox
@konsolebox, ¿de qué estás tratando de defenderte? Generalmente soy fanático de incluir condicionales lógicos, pero ¿cuál fue el error específico que estaba viendo en el pushd? Prefiero encontrar una forma de manejarlo directamente en lugar de devolver un SCRIPT_DIR vacío.
Fuwjax
@Fuwjax Práctica natural para evitar hacer popden casos (incluso cuando es raro) donde pushdfalla. Y en caso de que pushdfalle, ¿cuál crees que debería ser el valor SCRIPT_DIR? La acción puede variar dependiendo de lo que pueda parecer lógico o de lo que un usuario podría preferir, pero ciertamente, hacerlo popdes incorrecto.
konsolebox
Todos esos pushd popdpeligros podrían evitarse simplemente dejándolos caer y usando cd+ pwdencerrado en una sustitución de comando. SCRIPT_DIR=$(...)
Amit Naidu
16

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

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

No hay necesidad de usar BASH_SOURCEcuando $0contiene el nombre de archivo del script.

usuario1338062
fuente
3
a menos que el guión se haya obtenido. o 'fuente' en cuyo caso seguirá siendo el script que lo haya obtenido, o, si es desde la línea de comandos, '-bash' (inicio de sesión tty) o 'bash' (invocado a través de 'bash -l') o '/ bin / bash '(invocado como un shell interactivo sin inicio de sesión)
osirisgothra
Agregué un segundo par de citas alrededor de la dirnamellamada. Necesario si la ruta del directorio contiene espacios.
user1338062
14
#!/bin/sh
PRG="$0"

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

scriptdir=`dirname "$PRG"`
Chico mono
fuente
No lo he probado en diferentes sistemas. ¡Pero esta solución es la que funciona de inmediato al menos en Ubuntu, para mí!
Natus Drew
$0no funcionará para un script de origen
Amit Naidu
13

$_Vale la pena mencionar como una alternativa a $0. Si está ejecutando un script desde Bash, la respuesta aceptada se puede acortar a:

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

Tenga en cuenta que esta debe ser la primera declaración en su secuencia de comandos.

apuro
fuente
44
Se rompe si usted sourceo .el guión. En esas situaciones, $_contendría el último parámetro del último comando que ejecutó antes de .. $BASH_SOURCEFunciona todo el tiempo.
clacke
11

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

  • Rutas absolutas o rutas relativas
  • Enlaces suaves de archivos y directorios
  • Invocación como script, bash script, bash -c script, source script, o. script
  • Espacios, pestañas, nuevas líneas, unicode, etc. en directorios y / o nombre de archivo
  • Nombres de archivo que comienzan con un guión

Si está ejecutando desde Linux, parece que usar el procidentificador es la mejor solución para localizar la fuente totalmente resuelta del script que se está ejecutando actualmente (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 usando bash primitivos solamente, pero estoy de acuerdo con eso porque readlinksimplifica la tarea considerablemente. La echo Xsuma una Xal final de la cadena variable de modo que cualquier espacio en blanco final en el nombre del archivo no ser comido, y la sustitución de parámetros ${VAR%X}al final de la línea se deshace de la X. Debido a que readlinkagrega una nueva línea propia (que normalmente se comería en la sustitución de comandos si no fuera por nuestro truco anterior), también tenemos que deshacernos de eso. Esto se logra más fácilmente usando el $''esquema de citas, 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 con nombres desviados)

Lo anterior debe cubrir sus necesidades para localizar el script que se está ejecutando actualmente en Linux, pero si no tiene el procsistema de archivos a su disposición, o si está tratando de localizar la ruta completamente resuelta de algún otro archivo, entonces tal vez encuentra útil el siguiente código. Es solo una ligera modificación de la línea anterior. Si está jugando con directorios / nombres de archivo extraños, verifica la salida con ambos lsy readlinkes informativo, ya que lsgenerará rutas "simplificadas", sustituyendo ?cosas como líneas nuevas.

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
fuente
Me sale /dev/pts/30con bash en Ubuntu 14.10 Desktop.
Dan Dascalescu
@DanDascalescu ¿Usando el one-liner? ¿O el fragmento de código completo en la parte inferior? ¿Y le estabas dando algún nombre complicado?
billyjmc
La una línea más otra línea a echo $resolved, me guardé como d, chmod +x d, ./d.
Dan Dascalescu
@DanDascalescu La primera línea en su secuencia de comandos debe ser #!/bin/bash
billyjmc
10

Intenta usar:

real=$(realpath $(dirname $0))
eeerahul
fuente
1
Todo lo que quiero saber es, ¿por qué este camino no es bueno? No parecía malo y correcto para mí. ¿Alguien podría explicar por qué se ha rechazado?
Shou Ya
77
realpath no es una utilidad estándar.
Steve Bennett
2
En Linux, realpath es una utilidad estándar (parte del paquete GNU coreutils), pero no es un bash incorporado (es decir, una función proporcionada por el propio bash). Si está ejecutando Linux, este método probablemente funcionará, aunque lo sustituiría $0para ${BASH_SOURCE[0]}que funcione en cualquier lugar, incluso en una función.
Doug Richardson
3
El orden de las operaciones en esta respuesta es incorrecto. Usted necesita primero resolver el enlace simbólico, a continuación, hacer dirnameporque la última parte de $0puede ser un enlace simbólico que apunta a un archivo que no está en el mismo directorio que el enlace simbólico en sí. La solución descrita en esta respuesta solo obtiene la ruta del directorio donde se almacenó el enlace simbólico, no el directorio del destino. Además, a esta solución le faltan citas. No funcionará si la ruta contiene caracteres especiales.
hagello
3
dir="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"
Kostiantyn Ponomarenko
9

Pruebe la siguiente solución de compatibilidad cruzada:

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

ya que los comandos como realpatho readlinkpodrían no estar disponibles (dependiendo del sistema operativo).

Nota: en Bash, se recomienda usar en ${BASH_SOURCE[0]}lugar de $0, de lo contrario, la ruta puede romperse al obtener el archivo (source / .).

Alternativamente, puede 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ímala como está, de lo contrario imprima$PWD variable + argumento del nombre del archivo (sin ./prefijo)

Relacionado:

kenorb
fuente
Por favor explique más sobre la función realpath.
Chris
1
La realpathfunción @Chris toma 1 argumento. Si el argumento ya tiene una ruta absoluta, imprímala como está, de lo contrario imprima $PWD+ nombre de archivo (sin ./prefijo).
kenorb
Su solución de compatibilidad cruzada no funciona cuando el script está vinculado.
Jakub Jirutka
9

Creo que tengo este. Llego tarde a la fiesta, pero creo que algunos apreciarán que esté aquí si se encuentran con 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
Geoff Nixon
fuente
8

Estas son formas cortas de obtener información del script:

Carpetas y archivos:

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

Usando estos comandos:

    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`

Y obtuve esta salida:

     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

Ver también: https://pastebin.com/J8KjxrPF

Usuario8461
fuente
Creo que mi respuesta está bien porque es difícil encontrar una edición de trabajo simple. Aquí puede tomar el código que desee, por ejemplo, cd + pwd, dirname + realpath o dirname + readlink. No estoy seguro de que todas las partes existan antes y la mayoría de las respuestas son complejas y sobrecargadas. Aquí puedes extraer el código que te gusta usar. Al menos, no lo elimine, ya que lo necesito en el futuro: D
Usuario8461
8

Esto funciona en bash-3.2:

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

Si tiene un ~/bindirectorio en su $PATH, tiene Adentro de este directorio. Se origina el guión ~/bin/lib/B. Usted sabe dónde está el script incluido en relación con el original, en el libsubdirectorio, pero no dónde está en relación con el directorio actual del usuario.

Esto se resuelve con lo siguiente (dentro A):

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

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

Matt Tardiff
fuente
3
El punto whiches muy discutible. type, hashY otras órdenes internas hacen la misma cosa mejor en bash. whiches un poco más portátil, aunque en realidad no es el mismo que se whichusa en otros shells como tcsh, que lo tiene como un generador incorporado.
Restablezca a Monica Please
"Siempre"? De ningún modo. whichAl ser una herramienta externa, no tiene ninguna razón para creer que se comporta de manera idéntica al shell principal.
Charles Duffy
7

La mejor solución compacta en mi opinión sería:

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

No hay confianza en nada más que Bash. El uso de dirname, readlinky basenameeventualmente conducirá a problemas de compatibilidad, por lo que es mejor evitarlos si es posible.

AsymLabs
fuente
2
Probablemente debería añadir que roza a: "$( cd "$( echo "${BASH_SOURCE[0]%/*}/" )"; pwd )". Tendría problemas con el directorio raíz si no lo hace. Además, ¿por qué incluso tienes que usar echo?
konsolebox
dirnamey basenameestán estandarizados POSIX, entonces ¿por qué evitar usarlos? Enlaces: dirname,basename
myrdd
La prevención de dos bifurcaciones de proceso adicionales y la adhesión a las funciones integradas de shell pueden ser una de las razones.
Amit Naidu