cómo obtener el directorio de scripts en POSIX sh?

Tengo el siguiente código en mi script bash. Ahora quiero usarlo en POSIX sh. Entonces, ¿cómo convertirlo? Gracias.

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

La contraparte POSIX-shell ( sh ) de $BASH_SOURCE es $0 . ver abajo para información de fondo

Advertencia : La diferencia crucial es que si el script se está obteniendo (cargado en el shell actual con) , los fragmentos a continuación no funcionarán correctamente. explicación más abajo

Tenga en cuenta que he cambiado DIR a dir en los fragmentos a continuación, porque es mejor no usar nombres de mayúsculas en mayúsculas para evitar conflictos con variables de entorno y variables de shell especiales.
El prefijo CDPATH= toma el lugar de > /dev/null en el comando original: $CDPATH se establece en una cadena nula para garantizar que el cd nunca repita nada.

En el caso más simple, esto servirá (el equivalente al comando del OP ):

 dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) 

Si también desea resolver la ruta del directorio resultante a su objective final en caso de que el directorio y / o sus componentes sean enlaces simbólicos , agregue -P al comando pwd :

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

Advertencia : Esto NO es lo mismo que encontrar el verdadero directorio de origen del script :
Digamos que su script foo está enlazado a /usr/local/bin/foo en $PATH , pero su verdadera ruta es /foodir/bin/foo .
Lo anterior todavía informará /usr/local/bin , porque la resolución de enlace simbólico ( -P ) se aplica al directorio , /usr/local/bin , en lugar de al script en sí.

Para encontrar el verdadero directorio de origen del script, debe inspeccionar la ruta del script para ver si se trata de un enlace simbólico y, de ser así, seguir los enlaces simbólicos (cadena de) al último archivo de destino y luego extraer la ruta del directorio de la ruta canónica del archivo de destino .

El readlink -f GNU (mejor: readlink -e ) podría hacer eso por usted, pero readlink no es una utilidad POSIX.
Mientras que las plataformas BSD, incluido OSX, también tienen una utilidad de readlink , en OSX no es compatible con la funcionalidad de -f . Dicho esto, para mostrar cuán simple se vuelve la tarea si readlink -f está disponible: dir=$(dirname "$(readlink -f -- "$0")") .

De hecho, no hay una utilidad POSIX para resolver enlaces simbólicos de archivos . Hay formas de evitarlo, pero son engorrosos y no son completamente robustos:

La siguiente función de shell que cumple con POSIX implementa lo que hace el readlink -e GNU y es una solución razonablemente robusta que solo falla en dos casos extremos :

  • rutas con líneas nuevas incrustadas (muy raro)
  • nombres de archivos que contienen cadena literal -> (también raro)

Con esta función, denominada rreadlink , definida, lo siguiente determina la ruta de origen del directorio verdadero del script :

 dir=$(dirname -- "$(rreadlink "$0")") 

código fuente de rreadlink()colocar antes de las llamadas en scripts:

 rreadlink() ( # Execute the function in a *subshell* to localize variables and the effect of `cd`. target=$1 fname= targetDir= CDPATH= # Try to make the execution environment as predictable as possible: # All commands below are invoked via `command`, so we must make sure that `command` # itself is not redefined as an alias or shell function. # (Note that command is too inconsistent across shells, so we don't use it.) # `command` is a *builtin* in bash, dash, ksh, zsh, and some platforms do not even have # an external utility version of it (eg, Ubuntu). # `command` bypasses aliases and shell functions and also finds builtins # in bash, dash, and ksh. In zsh, option POSIX_BUILTINS must be turned on for that # to happen. { \unalias command; \unset -f command; } >/dev/null 2>&1 [ -n "$ZSH_VERSION" ] && options[POSIX_BUILTINS]=on # make zsh find *builtins* with `command` too. while :; do # Resolve potential symlinks until the ultimate target is found. [ -L "$target" ] || [ -e "$target" ] || { command printf '%s\n' "ERROR: '$target' does not exist." >&2; return 1; } command cd "$(command dirname -- "$target")" # Change to target dir; necessary for correct resolution of target path. fname=$(command basename -- "$target") # Extract filename. [ "$fname" = '/' ] && fname='' # !! curiously, `basename /` returns '/' if [ -L "$fname" ]; then # Extract [next] target path, which may be defined # *relative* to the symlink's own directory. # Note: We parse `ls -l` output to find the symlink target # which is the only POSIX-compliant, albeit somewhat fragile, way. target=$(command ls -l "$fname") target=${target#* -> } continue # Resolve [next] symlink target. fi break # Ultimate target reached. done targetDir=$(command pwd -P) # Get canonical dir. path # Output the ultimate target's canonical path. # Note that we manually resolve paths ending in /. and /.. to make sure we have a normalized path. if [ "$fname" = '.' ]; then command printf '%s\n' "${targetDir%/}" elif [ "$fname" = '..' ]; then # Caveat: something like /var/.. will resolve to /private (assuming /var@ -> /private/var), ie the '..' is applied # AFTER canonicalization. command printf '%s\n' "$(command dirname -- "${targetDir}")" else command printf '%s\n' "${targetDir%/}/$fname" fi ) 

Para ser robusto y predecible, la función utiliza el command para garantizar que solo se invoquen builtins de shell o utilidades externas (ignora las sobrecargas en las formas de alias y funciones).
Se ha probado en versiones recientes de las siguientes shells: bash , dash , ksh , zsh .


Cómo manejar las invocaciones originadas :

tl; dr :

Usando solo las características de POSIX :

  • No puede determinar la ruta del script en una invocación de origen (excepto en zsh , que, sin embargo, no suele actuar como sh ).
  • Puede detectar si el script se está obteniendo SÓLO si el script se está obteniendo directamente del shell (como en un perfil de shell / archivo de inicialización, posiblemente a través de una cadena de fonts), comparando $0 con el nombre ejecutable del shell / ruta (excepto en zsh , donde, como se señaló $0 es realmente la ruta del script actual). Por el contrario (excepto en zsh ), un script que proviene de otro script que se invocó directamente , contiene la ruta del script en $0 .
  • Para resolver estos problemas, bash , ksh y zsh tienen características no estándar que permiten determinar la ruta real del script incluso en escenarios originados y también detectar si un script se está obteniendo o no; por ejemplo, en bash , $BASH_SOURCE siempre contiene la ruta del script en ejecución, ya sea que se obtenga o no, y [[ $0 != "$BASH_SOURCE" ]] se puede usar para probar si el script se está obteniendo.

Para mostrar por qué no se puede hacer esto , analicemos el comando de la respuesta de Walter A :

  # NOT recommended - see discussion below. DIR=$( cd -P -- "$(dirname -- "$(command -v -- "$0")")" && pwd -P ) 
  • (Dos apartes:
    • Usar -P dos veces es redundante – es suficiente usarlo con pwd .
    • Al comando le falta el silenciamiento de la salida stdout potencial de cd , si se establece $CDPATH .)
  • command -v -- "$0"
    • command -v -- "$0" está diseñado para cubrir un escenario adicional: si el script se obtiene de un shell interactivo , $0 generalmente contiene el mero nombre de archivo del ejecutable ( sh ) del shell, en cuyo caso dirname simplemente devolverá . (porque eso es lo que invariablemente dirname cuando se le da un argumento sin un componente de ruta ). command -v -- "$0" luego devuelve la ruta absoluta de ese shell a través de una búsqueda $PATH ( /bin/sh ). Sin embargo, tenga en cuenta que los shells de inicio de sesión en algunas plataformas (p. Ej., OSX) tienen su nombre de archivo prefijado - en $0 ( -sh ), en cuyo caso el command -v -- "$0" no funciona como se esperaba (devuelve una cadena vacía )
    • Por el contrario, el command -v -- "$0" puede comportarse mal en dos escenarios sin origen en el que el ejecutable del shell, sh , se invoca directamente , con el script como argumento:
      • si el script en sí no es ejecutable: el command -v -- "$0" puede devolver una cadena vacía , dependiendo de qué shell específico actúa como sh en un sistema dado: bash , ksh y zsh devuelven una cadena vacía; solo dash echoes $0
        La especificación POSIX for command no dice explícitamente si el command -v , cuando se aplica a una ruta del sistema de archivos , solo debe devolver los archivos ejecutables , que es lo que hacen bash , ksh y zsh , pero se puede argumentar que está implícito en el propósito del command ; curiosamente, dash , que por lo general es el ciudadano POSIX más obediente, se está desviando del estándar aquí. Por el contrario, ksh es el único ciudadano modelo aquí, porque es el único que informa solo los archivos ejecutables y los informa con una ruta absoluta (aunque no normalizada), según lo requiera la especificación.
      • si la secuencia de comandos es ejecutable, pero no en $PATH , y la invocación usa su mero nombre de archivo (por ejemplo, sh myScript ), el command -v -- "$0" también devolverá la cadena vacía , excepto en el dash .
    • Dado que el directorio del script no se puede determinar cuando el script se está obteniendo , porque $0 no contiene esa información (excepto en zsh , que generalmente no actúa como sh ), no hay una buena solución para este problema.
      • La devolución de la ruta del directorio del ejecutable de la shell en esa situación es de uso limitado; después de todo, no es el directorio del script, excepto tal vez para usar posteriormente esa ruta en una prueba para determinar si el script se está obteniendo o no .
        • Un enfoque más confiable sería simplemente probar $0 directamente: [ "$0" = "sh" ] || [ "$0" = "-sh" ] || [ "$0" = "/bin/sh" ] [ "$0" = "sh" ] || [ "$0" = "-sh" ] || [ "$0" = "/bin/sh" ]
      • Sin embargo, incluso eso no funciona si el script proviene de otro script (que fue invocado directamente ), porque $0 simplemente contiene la ruta del sourcing sourcing .
    • Dada la utilidad limitada del command -v -- "$0" en los escenarios originados y el hecho de que rompe dos escenarios sin origen, mi voto es por NO usarlo , lo que nos deja con:
      • Todos los escenarios sin origen están cubiertos.
      • En las invocaciones de fonts , no puede determinar la ruta del script , y en el mejor de los casos, en circunstancias limitadas, puede detectar si se está produciendo o no el abastecimiento:
        • Cuando proviene directamente del shell (como desde un perfil de shell / archivo de inicialización), $dir termina conteniendo . , si el ejecutable de la shell fue invocado como un mero nombre de archivo (la aplicación de dirname a un mero nombre de archivo siempre retorna . ), o la ruta del directorio del ejecutable de la shell en caso contrario. . no se puede distinguir confiablemente de una invocación sin origen del directorio actual.
        • Cuando proviene de otra secuencia de comandos (que a su vez no es de origen), $0 contiene la ruta de esa secuencia de comandos, y la secuencia de comandos que se obtiene no tiene forma de decir si ese es el caso.

Información de fondo:

POSIX define el comportamiento de $0 con respecto a los scripts de shell aquí .

Básicamente, $0 debe reflejar la ruta del archivo de script como se especifica , lo que implica:

  • NO confíe en $0 contenga una ruta absoluta .
  • $0 contiene una ruta absoluta solo si:

    • especificas explícitamente una ruta absoluta ; p.ej:
      • ~/bin/myScript (suponiendo que el script en sí mismo sea ejecutable)
      • sh ~/bin/myScript
    • Invoca un script ejecutable por mera nombre de archivo , que requiere que ambos sean ejecutables y en $PATH ; detrás de escena, el sistema transforma myScript en una ruta absoluta y luego la ejecuta; p.ej:
      • myScript # executes /home/jdoe/bin/myScript, for instance
  • En todos los demás casos, $0 reflejará la ruta del script como se especifica :

    • Cuando se invoca explícitamente sh con un script, este puede ser un mero nombre de archivo (p. Ej., sh myScript ) o una ruta relativa (p. Ej., sh ./myScript )
    • Al invocar directamente un script ejecutable, esta puede ser una ruta relativa (por ejemplo, ./myScript , ./myScript cuenta que un mero nombre de archivo solo encontraría scripts en $PATH .

En la práctica, bash , dash , ksh y zsh exhiben este comportamiento.

Por el contrario, POSIX NO ordena el valor de $0 cuando obtiene un script (utilizando la utilidad especial incorporada . (“Punto”)), por lo que no puede confiar en él , y, en la práctica, el comportamiento difiere entre shells.

  • Por lo tanto, no puede usar ciegamente $0 cuando el script se está obteniendo y espera un comportamiento estandarizado.
    • En la práctica, bash , dash y ksh dejan intacto $0 al buscar scripts, lo que significa que $0 contiene el valor de $0 la persona que llama o, más exactamente, el valor de $0 de la llamada más reciente en la cadena de llamadas que no se ha obtenido; por lo tanto, $0 puede apuntar al archivo ejecutable del shell o a la ruta de otro script (invocado directamente) que originó el actual.
    • Por el contrario, zsh , como único desacuerdo, realmente informa la ruta del guión actual en $0 . Por el contrario, $0 no proporcionará ninguna indicación sobre si el script se está obteniendo o no.
    • En resumen: al utilizar solo las características de POSIX, no se puede saber con seguridad si el guión en cuestión se está obteniendo, ni cuál es el guión en cuestión, ni cuál es la relación de $0 con la ruta del guión actual.
  • Si necesita manejar esta situación, debe identificar el shell específico a mano y acceder a sus características específicas no estándar :
    • bash , ksh y zsh ofrecen sus propias formas de obtener la ruta del script en ejecución, incluso cuando se está obteniendo.

En aras de la exhaustividad: el valor de $0 en otros contextos :

  • Dentro de una función de shell , POSIX exige que $0 permanezcan sin cambios ; por lo tanto, cualquiera que sea el valor que tenga fuera de la función, también lo tendrá dentro .
    • En la práctica, bash , dash y ksh comportan de esa manera.
    • De nuevo, zsh es el único disidente e informa el nombre de la función .
  • En un shell que aceptó una cadena de comando mediante la opción -c al inicio, es el primer operando (argumento no opcional) que establece $0 ; p.ej:
    • sh -c 'echo \$0: $0 \$1: $1' foo one # -> '$0: foo $1: one'
    • bash , dash , ksh y zsh comportan de esa manera.
  • De lo contrario , en un shell que no ejecuta un archivo de script , $0 es el valor del primer argumento que pasó el proceso primario del shell ; generalmente, ese es el nombre o la ruta del shell (por ejemplo, sh , o /bin/sh ); esto incluye:
    • un caparazón interactivo
      • Advertencia : algunas plataformas, especialmente OSX, siempre crean shell de inicio de sesión al crear shells interactivos, y antependen al nombre del shell antes de colocarlo en $0 , para indicar al shell que es un shell de _login; por lo tanto, de manera predeterminada, $0 informa -bash , no bash , en shells interactivos en OSX.
    • un shell que lee comandos de stdin
      • esto también se aplica a la canalización de un archivo de script al shell a través de stdin (por ejemplo, sh < myScript )
    • bash , dash , ksh y zsh comportan de esa manera.

@ City respondió que

 DIR=$( cd -P -- "$(dirname -- "$(command -v -- "$0")")" && pwd -P ) 

trabajos. Lo usé también.
Encontré el comando en https://stackoverflow.com/questions/760110/can-i-get-the-absolute-path-to-the-cu rrent-script-in-kornshell .