¿Manera confiable para que un script bash obtenga el camino completo a sí mismo?

Tengo un script bash que necesita saber su camino completo. Estoy tratando de encontrar una manera ampliamente compatible de hacerlo sin terminar con caminos relativos o de aspecto funky. Solo necesito apoyar bash, no sh, csh, etc.

Lo que he encontrado hasta ahora:

  1. La respuesta aceptada a ” Obtener el directorio de origen de una secuencia de comandos Bash desde dentro ” trata de obtener la ruta de la secuencia de comandos a través de dirname $0 , lo cual está bien, pero puede devolver una ruta relativa (como . ), Que es un problema si lo desea para cambiar directorios en el script y hacer que la ruta siga apuntando al directorio del script. Aún así, dirname será parte del rompecabezas.

  2. La respuesta aceptada a ” Bash script absolute path with OSX ” (OS X específico, pero la respuesta funciona independientemente) da una función que probará para ver si $0 parece relativo y, en caso afirmativo, pendirá previamente $PWD . Pero el resultado puede tener bits relativos (aunque en general es absoluto); por ejemplo, si el script está en el directorio /usr/bin y estás en /usr y bin/../bin/t para ejecutarlo (sí, eso es enrevesado), terminas con /usr/bin/../bin como la ruta del directorio del script. Lo que funciona , pero …

  3. La solución readlink en esta página , que se ve así:

     # 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` 

    Pero readlink no es POSIX y aparentemente la solución se basa en el readlink de GNU, donde los BSD no funcionarán por alguna razón (no tengo acceso a un sistema BSD para verificar).

Entonces, varias formas de hacerlo, pero todas tienen sus advertencias.

¿Cuál sería una mejor manera? Donde “mejor” significa:

  • Me da el camino absoluto
  • Saca funky bits incluso cuando se invoca de una manera complicada (ver el comentario en el n. ° 2 arriba). (Por ejemplo, al menos moderadamente canonicaliza el camino).
  • Se basa solo en bash-ismos o cosas que casi con toda seguridad se encontrarán en los sabores más populares de los sistemas * nix (sistemas GNU / Linux, BSD y similares a BSD como OS X, etc.).
  • Evita llamar a progtwigs externos si es posible (por ejemplo, prefiere bash built-ins).
  • ( Actualizado , gracias por el aviso, lo cual ) No tiene que resolver los enlaces simbólicos (de hecho, preferiría que los dejara en paz, pero eso no es un requisito).

Esto es lo que se me ocurrió (editar: más algunos ajustes proporcionados por sfstewman , levigroker , Kyle Strand y Rob Kennedy ), que parece ajustarse a mis “mejores” criterios:

 SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )" 

Esa línea SCRIPTPATH parece particularmente indirecta, pero la necesitamos en lugar de SCRIPTPATH=`pwd` para gestionar adecuadamente los espacios y los enlaces simbólicos.

Tenga en cuenta también que las situaciones esotéricas, como ejecutar un script que no provenga de un archivo en un sistema de archivos accesible (lo cual es perfectamente posible), no se atiende allí (o en cualquiera de las otras respuestas que he visto )

Me sorprende que el comando realpath no se haya mencionado aquí. Tengo entendido que es ampliamente portable / portado.

Su solución inicial se convierte en:

 SCRIPT=`realpath $0` SCRIPTPATH=`dirname $SCRIPT` 

Y para dejar enlaces simbólicos sin resolver según su preferencia:

 SCRIPT=`realpath -s $0` SCRIPTPATH=`dirname $SCRIPT` 

La forma más sencilla que he encontrado para obtener una ruta canónica completa en bash es usar cd y pwd :

 ABSOLUTE_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" 

El uso de ${BASH_SOURCE[0]} lugar de $0 produce el mismo comportamiento independientemente de si el script se invoca como o source

Solo tuve que volver a visitar este tema hoy y encontré https://stackoverflow.com/a/246128/1034080 . Desarrolla una solución que he usado en el pasado también .

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

Hay más variantes en la respuesta vinculada, por ejemplo, para el caso en que el script en sí es un enlace simbólico.

Obtener la ruta absoluta del script de shell

No usa la opción -f en readlink, por lo tanto debería funcionar en bsd / mac-osx

Apoyos

  • source ./script (Cuando lo llama el operador . dot)
  • Ruta / ruta / a / script absolutas
  • Ruta relativa como ./script
  • /path/dir1/../dir2/dir3/../script
  • Cuando se llama desde symlink
  • Cuando el enlace simbólico está nested, por ejemplo) foo->dir1/dir2/bar bar->./../doe doe->script
  • Cuando la persona que llama cambia el nombre de las secuencias de comandos

Estoy buscando casos de esquina donde este código no funciona . Por favor hagamelo saber.

Código

 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`]" 

Conocido issuse

El script debe estar en el disco en alguna parte , déjelo en una red. Si intenta ejecutar este script desde un PIPE, no funcionará

 wget -o /dev/null -O - http://host.domain/dir/script.sh |bash 

Técnicamente hablando, no está definido.
En términos prácticos, no hay una manera sensata de detectar esto. (el proceso conjunto no puede acceder a env de los padres)

¿Qué hay de usar:

 SCRIPT_PATH=$(dirname `which $0`) 

which imprime para mostrar la ruta completa del ejecutable que se habría ejecutado cuando se ingresó el argumento pasado en el intérprete de comandos del shell (que es lo que contiene $ 0)

dirname el sufijo que no es de directorio del nombre de archivo

De ahí que termines con la ruta completa al script, sin importar si la ruta fue especificada o no.

Como realpath no está instalado por defecto en mi sistema Linux, lo siguiente funciona para mí:

 SCRIPT="$(readlink --canonicalize-existing "$0")" SCRIPTPATH="$(dirname "$SCRIPT")" 

$SCRIPT contendrá la ruta real del archivo al script y $SCRIPTPATH la ruta real del directorio que contiene el script.

Antes de usar esto, lea los comentarios de esta respuesta .

Respondiendo a esta pregunta muy tarde, pero uso:

 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 

Hemos colocado nuestro propio producto realpath-lib en GitHub para el uso de la comunidad libre y no comprometida.

Plug desvergonzado, pero con esta biblioteca de Bash puedes:

 get_realpath  

Esta función es el núcleo de la biblioteca:

 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 } 

No requiere dependencias externas, solo Bash 4+. También contiene funciones para get_dirname , get_filename , get_stemname y validate_path validate_realpath . Es gratis, limpio, simple y está bien documentado, por lo que también se puede utilizar con fines de aprendizaje y, sin duda, se puede mejorar. Pruébalo en todas las plataformas.

Actualización: Después de algunas revisiones y pruebas, hemos reemplazado la función anterior con algo que logra el mismo resultado (sin usar dirname, solo Bash puro) pero con una mejor eficiencia:

 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 } 

Esto también incluye una configuración de entorno no_symlinks que proporciona la capacidad de resolver enlaces simbólicos al sistema físico. Por defecto, mantiene los enlaces simbólicos intactos.

Puede intentar definir la siguiente variable:

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

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

Relacionado:

  • Ruta absoluta del script Bash con OSX

  • Obtener el directorio fuente de un script Bash desde dentro

sh manera compatible:

 SCRIPT_HOME=`dirname $0 | while read a; do cd $a && pwd && break; done` 

simplemente: BASEDIR=$(readlink -f $0 | xargs dirname)

sin operadores sofisticados

HIH.

Teniendo en cuenta este problema de nuevo: hay una solución muy popular a la que se hace referencia en este hilo que tiene su origen aquí :

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

Me he mantenido alejado de esta solución debido al uso de dirname; puede presentar dificultades multiplataforma, especialmente si una secuencia de comandos debe bloquearse por razones de seguridad. Pero como una alternativa pura de Bash, ¿qué tal si usas:

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

¿Esta sería una opción?

Tal vez la respuesta aceptada a la siguiente pregunta pueda ser de ayuda.

¿Cómo puedo obtener el comportamiento del readlink -f de GNU en una Mac?

Dado que solo quieres canonicalizar el nombre que obtienes al concatenar $ PWD y $ 0 (asumiendo que $ 0 no es absoluto para empezar) Solo usa una serie de reemplazos de abs_dir=${abs_dir//\/.\//\/} regulares a lo largo de la línea de abs_dir=${abs_dir//\/.\//\/} y tal.

Sí, sé que se ve horrible pero funcionará y es pura diversión.

La solución aceptada tiene el inconveniente (para mí) de no ser “fuente-capaz”:
si lo llamas desde una ” source ../../yourScript / source ../../yourScript “, ¡ $0 sería ” bash “!

La siguiente función (para bash> = 3.0) me da la ruta correcta, sin embargo, la secuencia de comandos podría ser llamada (directamente o a través de la source , con una ruta absoluta o relativa):
(por “ruta correcta”, me refiero a la ruta absoluta completa de la secuencia de comandos que se llama , incluso cuando se llama desde otra ruta, directamente o con ” 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 clave es detectar el caso de ” source ” y usar ${BASH_SOURCE[0]} para recuperar el guión real.

Si usamos Bash, creo que esta es la forma más conveniente, ya que no requiere llamadas a ningún comando externo:

 THIS_PATH="${BASH_SOURCE[0]}"; THIS_DIR=$(dirname $THIS_PATH) 

¿Fácil de leer? alternativa. Ignora los enlaces simbólicos

 #!/bin/bash currentDir=$( cd $(dirname "$0") pwd ) echo -n "current " pwd echo script $currentDir 

Prueba esto:

 cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0)) 

Solo por el placer de hacerlo he pirateado un guión que hace las cosas puramente textualmente, puramente en bash. Espero haber atrapado todos los casos extremos. Tenga en cuenta que ${var//pat/repl} que mencioné en la otra respuesta no funciona, ya que no puede hacer que reemplace solo la coincidencia más corta posible, que es un problema para reemplazar /foo/../ como por ejemplo, /*/../ tomará todo lo anterior, no solo una entrada. Y dado que estos patrones no son realmente expresiones regulares, no veo cómo se puede hacer para que funcionen. Así que aquí está la solución muy complicada que se me ocurrió, disfruta. 😉

Por cierto, avíseme si encuentra casos extremos no controlados.

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

He utilizado el siguiente enfoque con éxito por un tiempo (aunque no en OSX) y solo usa shell incorporado y maneja el caso ‘source foobar.sh’ por lo que he visto.

Un problema con el código de ejemplo (rápidamente creado) a continuación es que la función utiliza $ PWD, que puede o no ser correcta en el momento de la llamada a la función. Entonces eso necesita ser manejado.

 #!/bin/bash function canonical_path() { # Handle realtive 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 

Otra forma de hacer esto:

 shopt -s extglob selfpath=$0 selfdir=${selfpath%%+([!/])} while [[ -L "$selfpath" ]];do selfpath=$(readlink "$selfpath") if [[ ! "$selfpath" =~ ^/ ]];then selfpath=${selfdir}${selfpath} fi selfdir=${selfpath%%+([!/])} done echo $selfpath $selfdir 

Un trazador de líneas

 `dirname $(realpath $0)` 

Más simplemente, esto es lo que funciona para mí:

 MY_DIR=`dirname $0` source $MY_DIR/_inc_db.sh