¿Una forma confiable para que un script bash obtenga el camino completo hacia sí mismo? [duplicar]


Esta pregunta ya tiene una respuesta aquí:

Tengo un script bash que necesita saber su ruta completa. Estoy tratando de encontrar una forma ampliamente compatible de hacer eso sin terminar con caminos de aspecto familiar o funky. Sólo necesito apoyar bash, no sh, csh, sucesivamente.

Lo que he encontrado hasta ahora:{[15]]}

  1. La respuesta aceptada a " Getting the source directory of a Bash script from within" se dirige a obtener la ruta del script a través de dirname $0, lo cual está bien, pero eso puede devolver una ruta relativa (como .), lo cual es un problema si desea cambiar los directorios en el script y que la ruta siga apuntando al directorio del script. Aún así, dirname será parte del rompecabezas.

  2. Las aceptadas respuesta a " Ruta absoluta del script Bash con OSX" (OS X específico, pero la respuesta funciona independientemente) da una función que probará para ver si $0 parece relativa y si es así pre-pend $PWD a ella. Pero el resultado todavía puede tener bits relativos (aunque en general es absoluto) - por ejemplo, si el script está t en el directorio /usr/bin y estás en /usr y escribes bin/../bin/t para ejecutarlo (sí, eso es enrevesado), terminas con /usr/bin/../bin como la ruta del directorio del script. 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 BSD no funcionará por alguna razón (no tengo acceso a un sistema similar a 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 la camino absoluto.
  • Saca bits funky incluso cuando se invoca de una manera enrevesada (ver comentario sobre #2 arriba). (Por ejemplo, al menos moderadamente canoniza el camino.)
  • Se basa solo en bash-isms o cosas que son casi seguros que estarán en los sabores más populares de los sistemas *nix (GNU/Linux, BSD y sistemas similares a BSD como OS X, etc.).
  • Evita llamar a programas externos si es posible (por ejemplo, prefiere bash built-ins).
  • (Actualizado, gracias por el aviso, wich ) No tiene que resolver enlaces simbólicos (de hecho, preferiría dejarlos solos, pero eso no es un requisito).
 526
Author: Community, 2011-01-23

23 answers

Esto es lo que se me ha ocurrido (editar: además de algunos ajustes proporcionados por sfstewman, levigroker, Kyle Strand , y Rob Kennedy ), que parece encajar en su mayoría mis "mejores" criterios:

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

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

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

 417
Author: T.J. Crowder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-07 06:40:00

Me sorprende que el comando realpath no haya sido mencionado aquí. Mi entendimiento es 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`
 160
Author: Darshan Rivka Whittle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-06-20 07:11:49

La forma más sencilla que he encontrado para obtener un camino canónico completo en bash es usar cd y pwd:

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

Usar ${BASH_SOURCE[0]} en lugar de $0 produce el mismo comportamiento independientemente de si el script se invoca como <name> o source <name>

 138
Author: Andrew Norrie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-18 19:41:47

Tuve que volver a este tema hoy y encontré https://stackoverflow.com/a/246128/1034080 . Elabora una solución que he utilizado 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 el que el script en sí es un enlace simbólico.

 39
Author: Felix Rabe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:48

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

Soporta

  • fuente ./ script (Cuando es llamado por el operador de punto .)
  • Ruta absoluta /ruta/a/script
  • Camino relativo como./ script
  • /path/dir1/../ dir2 / dir3/../ script
  • Cuando se llama desde symlink
  • Cuando el enlace simbólico está anidado eg) foo->dir1/dir2/bar bar->./../doe doe->script
  • Cuando el llamante cambia los scripts nombre

Estoy buscando casos de esquina donde este código no funciona . Por favor, házmelo 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`]"

Issuse conocido

El script debe estar en algún lugar del disco, que sea a través de una red. Si intenta ejecutar este script desde una TUBERÍA, no funcionará

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

Técnicamente hablando, no está definido.
En la práctica, no hay una manera sana de detectar esto. (co-proceso no puede acceder env de padre)

 35
Author: GreenFox,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-11-29 18:10:34

Qué hay de usar:

SCRIPT_PATH=$(dirname `which $0`)

which imprime en stdout la ruta completa del ejecutable que se habría ejecutado cuando el argumento pasado se hubiera introducido en el prompt del shell (que es lo que contiene $0)

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

Por lo tanto, lo que termina con es la ruta completa al script, sin importar si la ruta se especificó o no.

 30
Author: Matt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-04 18:21:05

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.

 24
Author: ypid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:02:48

Responder esta pregunta muy tarde, pero yo 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
 11
Author: Stormcloud,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-03-24 16:11:39

Hemos colocado nuestro propio producto realpath-lib en GitHub para uso gratuito y sin trabas de la comunidad.

Plug desvergonzada pero con esta biblioteca Bash usted puede:

get_realpath <absolute|relative|symlink|local file>

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 ninguna dependencia externa, solo Bash 4+. También contiene funciones para get_dirname, get_filename, get_stemname y validate_path validate_realpath. Es gratis, limpio, simple y bien documentado, por lo que también se puede usar 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 pure Bash) pero con 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 intactos los enlaces simbólicos.

 7
Author: AsymLabs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-05 13:15:16

Puede intentar definir la siguiente variable:

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

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

Relacionado:

 5
Author: kenorb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:18:23

Sh forma compatible:

SCRIPT_HOME=`dirname $0 | while read a; do cd $a && pwd && break; done`
 5
Author: Haruo Kinoshita,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-11 18:48:34

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

No hay operadores de lujo

HIH.

 4
Author: poussma,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-03 13:59:03

Quizás la respuesta aceptada a la siguiente pregunta pueda ser de ayuda.

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

Dado que solo desea canonizar el nombre que obtiene al concatenar $PWD y 0 0 (suponiendo que $0 no es absoluto para empezar) Solo use una serie de reemplazos de expresiones 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 puro bash.

 3
Author: wich,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 10:31:37

Considerando este problema de nuevo: hay una solución muy popular que se hace referencia dentro de 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, particularmente si un script necesita ser bloqueado por razones de seguridad. Pero como una alternativa pura Bash, ¿qué tal usar:

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

¿Sería esta una opción?

 3
Author: AsymLabs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:48

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

#!/bin/bash
currentDir=$(
  cd $(dirname "$0")
  pwd
) 

echo -n "current "
pwd
echo script $currentDir 
 3
Author: gerardw,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-12 18:43:39

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

La siguiente función (para bash >= 3.0) me da la ruta correcta, sin embargo el script podría ser llamado (directamente o a través de source, con una ruta absoluta o relativa):
(por "camino correcto", me refiero a la ruta absoluta completa del script que se llama , incluso cuando se llama desde otro camino, 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 "source" y usar ${BASH_SOURCE[0]} para recuperar el script real.

 2
Author: VonC,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-06-24 02:45:06

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)
 2
Author: Nordlöw,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-18 20:05:31

Prueba esto:

cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0))
 1
Author: diyism,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-29 03:05:05

Solo por el infierno de que he hecho un poco de hacking en un script que hace las cosas puramente textualmente, puramente en bash. Espero haber cogido todos los casos extremos. Tenga en cuenta que el ${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, lo que es un problema para reemplazar /foo/../ ya que, por ejemplo, /*/../ tomará todo lo anterior, no solo una sola entrada. Y ya que estos patrones no son realmente expresiones regulares no veo cómo se puede hacer que funcione. Tan aquí está la solución bien enrevesada que se me ocurrió, disfrutar. ;)

Por cierto, hágamelo saber si encuentra algún caso de borde no manejado.

#!/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/"
 0
Author: wich,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-23 14:53:44

He utilizado el siguiente enfoque con éxito durante un tiempo (sin embargo, no en OSX) y solo utiliza shell integrado y maneja el ' source foobar.sh por lo que he visto.

Un problema con el código de ejemplo (rápido) a continuación es que la función utiliza PW PWD que puede o no ser correcta en el momento de la llamada a la función. Así que 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
 0
Author: andro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-18 21:38:57

Otra manera 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
 -1
Author: Meow,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-19 14:02:50

Un revestimiento

`dirname $(realpath $0)`
 -1
Author: Abhijit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-18 02:02:32

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

MY_DIR=`dirname $0`
source $MY_DIR/_inc_db.sh
 -4
Author: Elendurwen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-15 11:31:48