¿Unix shell script descubre en qué directorio reside el archivo de script?
511
Básicamente, necesito ejecutar el script con rutas relacionadas con la ubicación del archivo de script de shell, ¿cómo puedo cambiar el directorio actual al mismo directorio donde reside el archivo de script?
¿Es eso realmente un duplicado? Esta pregunta es sobre un "script de shell de Unix", la otra específicamente sobre Bash.
michaeljt
2
@BoltClock: esta pregunta se cerró incorrectamente. La pregunta vinculada es sobre Bash. Esta pregunta es sobre la programación de shell de Unix. ¡Tenga en cuenta que las respuestas aceptadas son bastante diferentes!
Dietrich Epp
@Dietrich Epp: Tienes razón. Parece que la elección del autor de la respuesta aceptada y la adición de la etiqueta [bash] (probablemente en respuesta a eso) me llevaron a marcar la pregunta como un duplicado en respuesta a una bandera.
Esto no funciona si ha llamado al script a través de un enlace simbólico en un directorio diferente. Para que eso funcione, también debe usarlo readlink(consulte la respuesta de todos a continuación)
AndrewR
44
En bash es más seguro usarlo $BASH_SOURCEen lugar de $0, porque $0no siempre contiene la ruta del script que se invoca, como cuando se 'obtiene' un script.
mklement0
2
$BASH_SOURCEes específico de Bash, la pregunta es sobre el script de shell en general.
Ha-Duong Nguyen
77
@auraham: ¡ CUR_PATH=$(pwd)o pwddevuelve el directorio actual (que no tiene que ser el directorio principal de los scripts)!
Andreas Dietrich
55
Probé el método @ mklement0 recomendado, usando $BASH_SOURCE, y devuelve lo que necesitaba. Mi secuencia de comandos se llama desde otra secuencia de comandos y $0regresa .mientras que $BASH_SOURCEdevuelve el subdirectorio correcto (en mi caso scripts).
David Rissato Cruz
402
La publicación original contiene la solución (ignore las respuestas, no agregan nada útil). El interesante trabajo lo realiza el mencionado comando unix readlinkcon opción -f. Funciona cuando el guión es invocado por una ruta absoluta o relativa.
Para 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
Para tcsh, csh:
#!/bin/tcsh# Absolute path to this script, e.g. /home/user/bin/foo.cshset SCRIPT=`readlink -f "$0"`# Absolute path this script is in, thus /home/user/binset SCRIPTPATH=`dirname "$SCRIPT"`
echo $SCRIPTPATH
Para aclarar el comentario de @ Ergwun: -fno es compatible con OS X (a partir de Lion); allí puede soltar la -ftarea de resolver como máximo un nivel de indirección, por ejemplo pushd "$(dirname "$(readlink "$BASH_SOURCE" || echo "$BASH_SOURCE")")", o puede rodar su propio script recursivo de seguimiento de enlaces simbólicos como se demuestra en la publicación vinculada.
mklement0
2
Todavía no entiendo por qué el OP necesitaría el camino absoluto. Reportando "." debería funcionar bien si desea acceder a los archivos relativos a la ruta de los scripts y llamó al script como ./myscript.sh
Stefan Haberl
10
@StefanHaberl Creo que sería un problema si ejecutaras el script mientras tu directorio de trabajo actual fuera diferente de la ubicación del script (por ejemplo sh /some/other/directory/script.sh), en este caso .sería tu pwd, no/some/other/directory
Jon z
51
Un comentario anterior sobre una respuesta lo dijo, pero es fácil pasarlo por alto entre todas las otras respuestas.
Al usar bash:
echo this file:"$BASH_SOURCE"
echo this dir:"$(dirname "$BASH_SOURCE")"
Solo este funciona con script en la ruta del entorno, los más votados no funcionan. ¡gracias!
julio
En su dirname "$BASH_SOURCE"lugar, debe usar para manejar espacios en $ BASH_SOURCE.
Mygod
1
Según la herramienta ShellCheck, una forma más explícita de imprimir el directorio sería: "$ (dirname" $ {BASH_SOURCE {0}} ")" porque BASH_SOURCE es una matriz y, sin el subíndice, el primer elemento se toma de forma predeterminada .
Adrian M.
1
@AdrianM. , quiere corchetes, no llaves, para el índice:"$(dirname "${BASH_SOURCE[0]}")"
Hashbrown
No es bueno para mí ... imprime solo un punto (es decir, el directorio actual, presumiblemente)
Este script debe imprimir el directorio en el que se encuentra, y luego el directorio en el que se encuentra el script. Por ejemplo, al llamarlo /con el script en /home/mez/, se genera
//home/mez
Recuerde, cuando asigne variables desde la salida de un comando, envuelva el comando $(y )- o no obtendrá la salida deseada.
Esto no funcionará cuando invoque el script desde el directorio actual.
Eric Wang
1
@EricWang siempre estás en el directorio actual.
ctrl-alt-delor
Para mí, $ current_dir es de hecho la ruta desde la que llamo al script. Sin embargo, $ script_dir no es el directorio del script, es solo un punto.
Michael
31
Si estás usando 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}"
Puede reemplazar el pushd/ popdcon cd $(dirname "${0}")y cd -hacer que funcione en otros shells, si tienen un pwd -L.
docwhat
¿Por qué usarías pushd y popd aquí?
qodeninja
1
Por lo tanto, no tengo que almacenar el directorio original en una variable. Es un patrón que uso mucho en funciones y demás. Anida realmente bien, lo cual es bueno.
docwhat
Todavía se está almacenando en la memoria, en una variable, ya sea que se haga referencia a una variable en su script o no. Además, creo que el costo de ejecutar pushd y popd supera con creces los ahorros de no crear una variable Bash local en su script, tanto en ciclos de CPU como en legibilidad.
ingyhere
20
Como sugiere el Marko:
BASEDIR=$(dirname $0)
echo $BASEDIR
Esto funciona a menos que ejecute el script desde el mismo directorio donde reside el script, en cuyo caso obtendrá un valor de '.'
Ahora puede usar la variable current_dir a lo largo de su script para referirse al directorio del script. Sin embargo, esto aún puede tener el problema del enlace simbólico.
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}")" && pwd )"
One-liner que le dará el nombre completo del directorio de la secuencia de comandos sin importar de dónde se llame.
Para entender cómo funciona, puede ejecutar el siguiente script:
#!/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 locatedfidone
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'"
como está escrito, cd: too many argumentssi espacios en la ruta, y vuelve $PWD. (una solución obvia, pero solo muestra cuántos casos extremos realmente hay)
michael
1
Votaría. Excepto por el comentario de @michael de que falla con espacios en la ruta ... ¿Hay alguna solución para eso?
Esto funciona tanto en Linux como en macOS. No pude ver a nadie aquí mencionar realpath. No estoy seguro de si hay algún inconveniente en este enfoque.
en macOS, necesita instalar coreutilspara usar realpath. Por ejemplo: brew install coreutils.
¿POR QUÉ EL USO DE dirname "$ 0" EN SU PROPIO NO FUNCIONA?
dirname $ 0 solo funcionará si el usuario inicia el script de una manera muy específica. Pude encontrar varias situaciones en las que esta respuesta falla y bloquea el script.
En primer lugar, comprendamos cómo funciona esta respuesta. Está obteniendo el directorio de script haciendo
dirname "$0"
$ 0 representa la primera parte del comando que llama al script (es básicamente el comando ingresado sin los argumentos:
/some/path/./script argument1 argument2
$ 0 = "/ some / path /./ script"
dirname básicamente encuentra el último / en una cadena y lo trunca allí. Entonces si lo haces:
dirname /usr/bin/sha256sum
obtendrá: / usr / bin
Este ejemplo funciona bien porque / usr / bin / sha256sum es una ruta formateada correctamente pero
dirname "/some/path/./script"
no funcionaría bien y te daría:
BASENAME="/some/path/."#which would crash your script if you try to use it as a path
Digamos que está en el mismo directorio que su script y lo inicia con este comando
./script
$ 0 en esta situación será ./script y dirname $ 0 dará:
.#or BASEDIR=".", again this will crash your script
Utilizando:
sh script
Sin ingresar la ruta completa también dará un BASEDIR = "."
Usando directorios relativos:
../some/path/./script
Da un nombre de directorio $ 0 de:
../some/path/.
Si está en el directorio / some y llama al script de esta manera (observe la ausencia de / al principio, nuevamente una ruta relativa):
path/./script.sh
Obtendrá este valor para dirname $ 0:
path/.
y ./path/./script (otra forma de la ruta relativa) da:
./path/.
Las únicas dos situaciones en las que trabajará con base $ 0 es si el usuario usa sh o touch para iniciar un script porque ambas generarán $ 0:
$0=/some/path/script
que le dará una ruta que puede usar con dirname.
LA SOLUCIÓN
Debería tener en cuenta y detectar cada una de las situaciones mencionadas anteriormente y aplicar una solución si surge:
#!/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 scriptif["$debug"= true ];then echo 'BASEDIR=$(dirname "$0") gives: '"$BASEDIR";fiif["$BASEDIR"="."];then BASEDIR="$(pwd)";fi# fix for situation1
_B2=${BASEDIR:$((${#BASEDIR}-2))}; B_=${BASEDIR::1}; B_2=${BASEDIR::2}; B_3=${BASEDIR::3}# <- bash onlyif["$_B2"="/."];then BASEDIR=${BASEDIR::$((${#BASEDIR}-1))};fi#fix for situation2 # <- bash onlyif["$B_"!="/"];then#fix for situation3 #<- bash onlyif["$B_2"="./"];then#covers ./relative_path/(./)scriptif["$(pwd)"!="/"];then BASEDIR="$(pwd)/${BASEDIR:2}";else BASEDIR="/${BASEDIR:2}";fielse#covers relative_path/(./)script and ../relative_path/(./)script, using ../relative_path fails if current path is a symbolic linkif["$(pwd)"!="/"];then BASEDIR="$(pwd)/$BASEDIR";else BASEDIR="/$BASEDIR";fififiif["$debug"= true ];then echo "fixed BASEDIR=$BASEDIR";fi
Esta línea le dice dónde está el script de shell, no importa si lo ejecutó o si lo obtuvo . Además, resuelve los enlaces simbólicos involucrados, si ese es el caso:
Tantas respuestas, todas plausibles, cada una con ventajas y desventajas y objetivos ligeramente diferentes (que probablemente deberían establecerse para cada una). Aquí hay otra solución que cumple con el objetivo principal de ser claro y trabajar en todos los sistemas, en todos los bash (sin suposiciones sobre las versiones readlinku pwdopciones de bash ), y hace razonablemente lo que esperaría que suceda (por ejemplo, resolver enlaces simbólicos es un problema interesante, pero generalmente no es lo que realmente quieres), maneja casos extremos como espacios en las rutas, etc., ignora cualquier error y usa un valor predeterminado sensato si hay algún problema.
Cada componente se almacena en una variable separada que puede usar individualmente:
# 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)"
Puede parecer feo dependiendo de cómo se invocó y el cwd, pero debería llevarlo a donde necesita ir (o puede ajustar la cadena si le importa cómo se ve).
Respuestas:
En Bash, deberías obtener lo que necesitas así:
fuente
readlink
(consulte la respuesta de todos a continuación)$BASH_SOURCE
en lugar de$0
, porque$0
no siempre contiene la ruta del script que se invoca, como cuando se 'obtiene' un script.$BASH_SOURCE
es específico de Bash, la pregunta es sobre el script de shell en general.CUR_PATH=$(pwd)
opwd
devuelve el directorio actual (que no tiene que ser el directorio principal de los scripts)!$BASH_SOURCE
, y devuelve lo que necesitaba. Mi secuencia de comandos se llama desde otra secuencia de comandos y$0
regresa.
mientras que$BASH_SOURCE
devuelve el subdirectorio correcto (en mi casoscripts
).La publicación original contiene la solución (ignore las respuestas, no agregan nada útil). El interesante trabajo lo realiza el mencionado comando unix
readlink
con opción-f
. Funciona cuando el guión es invocado por una ruta absoluta o relativa.Para bash, sh, ksh:
Para tcsh, csh:
Ver también: https://stackoverflow.com/a/246128/59087
fuente
readlink
. Es por eso que recomendé usar pushd / popd (incorporados para bash).-f
opción dereadlink
hacer algo diferente en OS X (Lion) y posiblemente BSD. stackoverflow.com/questions/1055671/…-f
no es compatible con OS X (a partir de Lion); allí puede soltar la-f
tarea de resolver como máximo un nivel de indirección, por ejemplopushd "$(dirname "$(readlink "$BASH_SOURCE" || echo "$BASH_SOURCE")")"
, o puede rodar su propio script recursivo de seguimiento de enlaces simbólicos como se demuestra en la publicación vinculada.sh /some/other/directory/script.sh)
, en este caso.
sería tu pwd, no/some/other/directory
Un comentario anterior sobre una respuesta lo dijo, pero es fácil pasarlo por alto entre todas las otras respuestas.
Al usar bash:
Manual de referencia de Bash, 5.2 Variables de Bash
fuente
dirname "$BASH_SOURCE"
lugar, debe usar para manejar espacios en $ BASH_SOURCE."$(dirname "${BASH_SOURCE[0]}")"
Asumiendo que estás usando bash
Este script debe imprimir el directorio en el que se encuentra, y luego el directorio en el que se encuentra el script. Por ejemplo, al llamarlo
/
con el script en/home/mez/
, se generaRecuerde, cuando asigne variables desde la salida de un comando, envuelva el comando
$(
y)
- o no obtendrá la salida deseada.fuente
Si estás usando bash ...
fuente
pushd
/popd
concd $(dirname "${0}")
ycd -
hacer que funcione en otros shells, si tienen unpwd -L
.Como sugiere el Marko:
Esto funciona a menos que ejecute el script desde el mismo directorio donde reside el script, en cuyo caso obtendrá un valor de '.'
Para solucionar ese problema, use:
Ahora puede usar la variable current_dir a lo largo de su script para referirse al directorio del script. Sin embargo, esto aún puede tener el problema del enlace simbólico.
fuente
La mejor respuesta para esta pregunta se respondió aquí:
Obtener el directorio fuente de un script Bash desde dentro
Y es:
One-liner que le dará el nombre completo del directorio de la secuencia de comandos sin importar de dónde se llame.
Para entender cómo funciona, puede ejecutar el siguiente script:
fuente
fuente
Hagámoslo un POSIX oneliner:
Probado en muchos shells compatibles con Bourne, incluidos los BSD.
Hasta donde sé, soy el autor y lo puse en dominio público. Para obtener más información, consulte: https://www.jasan.tk/posts/2017-05-11-posix_shell_dirname_replacement/
fuente
cd: too many arguments
si espacios en la ruta, y vuelve$PWD
. (una solución obvia, pero solo muestra cuántos casos extremos realmente hay)fuente
Si desea obtener el directorio de script real (independientemente de si está invocando el script usando un enlace simbólico o directamente), intente:
Esto funciona tanto en Linux como en macOS. No pude ver a nadie aquí mencionar
realpath
. No estoy seguro de si hay algún inconveniente en este enfoque.en macOS, necesita instalar
coreutils
para usarrealpath
. Por ejemplo:brew install coreutils
.fuente
INTRODUCCIÓN
Esta respuesta corrige la respuesta muy rota pero sorprendentemente mejor votada de este hilo (escrita por TheMarko):
¿POR QUÉ EL USO DE dirname "$ 0" EN SU PROPIO NO FUNCIONA?
dirname $ 0 solo funcionará si el usuario inicia el script de una manera muy específica. Pude encontrar varias situaciones en las que esta respuesta falla y bloquea el script.
En primer lugar, comprendamos cómo funciona esta respuesta. Está obteniendo el directorio de script haciendo
$ 0 representa la primera parte del comando que llama al script (es básicamente el comando ingresado sin los argumentos:
$ 0 = "/ some / path /./ script"
dirname básicamente encuentra el último / en una cadena y lo trunca allí. Entonces si lo haces:
obtendrá: / usr / bin
Este ejemplo funciona bien porque / usr / bin / sha256sum es una ruta formateada correctamente pero
no funcionaría bien y te daría:
Digamos que está en el mismo directorio que su script y lo inicia con este comando
$ 0 en esta situación será ./script y dirname $ 0 dará:
Utilizando:
Sin ingresar la ruta completa también dará un BASEDIR = "."
Usando directorios relativos:
Da un nombre de directorio $ 0 de:
Si está en el directorio / some y llama al script de esta manera (observe la ausencia de / al principio, nuevamente una ruta relativa):
Obtendrá este valor para dirname $ 0:
y ./path/./script (otra forma de la ruta relativa) da:
Las únicas dos situaciones en las que trabajará con base $ 0 es si el usuario usa sh o touch para iniciar un script porque ambas generarán $ 0:
que le dará una ruta que puede usar con dirname.
LA SOLUCIÓN
Debería tener en cuenta y detectar cada una de las situaciones mencionadas anteriormente y aplicar una solución si surge:
fuente
Esta línea le dice dónde está el script de shell, no importa si lo ejecutó o si lo obtuvo . Además, resuelve los enlaces simbólicos involucrados, si ese es el caso:
Por cierto, supongo que estás usando / bin / bash .
fuente
Tantas respuestas, todas plausibles, cada una con ventajas y desventajas y objetivos ligeramente diferentes (que probablemente deberían establecerse para cada una). Aquí hay otra solución que cumple con el objetivo principal de ser claro y trabajar en todos los sistemas, en todos los bash (sin suposiciones sobre las versiones
readlink
upwd
opciones de bash ), y hace razonablemente lo que esperaría que suceda (por ejemplo, resolver enlaces simbólicos es un problema interesante, pero generalmente no es lo que realmente quieres), maneja casos extremos como espacios en las rutas, etc., ignora cualquier error y usa un valor predeterminado sensato si hay algún problema.Cada componente se almacena en una variable separada que puede usar individualmente:
fuente
Inspirado por la respuesta de blueyed
fuente
Eso debería hacer el truco:
Puede parecer feo dependiendo de cómo se invocó y el cwd, pero debería llevarlo a donde necesita ir (o puede ajustar la cadena si le importa cómo se ve).
fuente
`pwd`/`dirname $0`
pero aún puede fallar en enlaces simbólicos