¿Manera portátil de obtener la ruta absoluta del script?

29

¿Cuál es una forma portátil para que un script (zsh) determine su ruta absoluta?

En Linux uso algo como

mypath=$(readlink -f $0)

... pero esto no es portátil. (Por ejemplo, readlinken Darwin no reconoce la -fbandera, ni tiene ningún equivalente.) (Además, usar readlinkpara esto es, ciertamente, un truco de aspecto bastante oscuro).

¿Qué es una forma más portátil?

kjo
fuente
1
Consulte también ¿$ 0 siempre incluirá la ruta al script?
Stéphane Chazelas

Respuestas:

28

Con zsh, es solo:

mypath=$0:A

Sin embargo, ahora para otros shells realpath()y readlink()son funciones estándar (esta última es una llamada al sistema), realpathy readlinkno son comandos estándar, aunque algunos sistemas tienen uno u otro o ambos con diferentes comportamientos y características.

Como a menudo, para la portabilidad, es posible que desee recurrir a perl:

abs_path() {
  perl -MCwd -le '
    for (@ARGV) {
      if ($p = Cwd::abs_path $_) {
        print $p;
      } else {
        warn "abs_path: $_: $!\n";
        $ret = 1;
      }
    }
    exit $ret' "$@"
}

Eso se comportaría más como GNU readlink -fque realpath()(GNU readlink -e) en el sentido de que no se quejará si el archivo no existe mientras su nombre de directorio sí lo haga.

Stéphane Chazelas
fuente
Nota: esto no funciona para su .zshrc: vea esta publicación en su lugar.
Bryce Guinta
24

En zsh puedes hacer lo siguiente:

mypath=${0:a}

O, para obtener el directorio en el que reside el script:

mydir=${0:a:h}

Fuente: página del comando man zshexpn (1), sección EXPANSIÓN DE HISTORIA, Modificadores de subsección (o simplemente info -f zsh -n Modifiers).

mrmenken
fuente
¡Dulce! He estado buscando algo así durante años y leí todo el montón de páginas de manual de zsh buscándolo, pero nunca se me habría ocurrido buscar en 'Expansión de la historia'.
Vucar Timnärakrul
1
El equivalente de GNU readlink -fpreferiría ser $0:A.
Stéphane Chazelas
11

He estado usando esto durante varios años:

# The absolute, canonical ( no ".." ) path to this script
canonical=$(cd -P -- "$(dirname -- "$0")" && printf '%s\n' "$(pwd -P)/$(basename -- "$0")")
usuario17591
fuente
1
¡me gusta! Hasta ahora, eso es bastante portátil. funciona en Solaris, OmniOS, Linux, Mac e incluso Cygwin en Windows 2008.
Tim Kennedy
44
Donde no es equivalente a GNU readlink -fes cuando el script en sí es un enlace simbólico.
Stéphane Chazelas
En Ubuntu 16.04 con zsh, si ejecuto esto directamente o en el subshell (como se sugiere) mientras estoy en mi directorio de inicio ( /home/ville), se imprime /home/ville/zsh.
Ville
8

Esta sintaxis debe ser portátil a cualquier intérprete estilo shell Bourne (probado con bash, ksh88, ksh93, zsh, mksh, dashy busybox sh):

mypath=$(exec 2>/dev/null;cd -- $(dirname "$0"); unset PWD; /usr/bin/pwd || /bin/pwd || pwd)
echo mypath=$mypath

Esta versión agrega compatibilidad con el shell Bourne de AT&T heredado (no POSIX):

mypath=`dirname "$0"`
mypath=`exec 2>/dev/null;(cd -- "$mypath") && cd -- "$mypath"|| cd "$mypath"; unset PWD; /usr/bin/pwd || /bin/pwd || pwd`
echo mypath=$mypath
jlliagre
fuente
Gracias. Sin embargo, creo que desestabilizar $PWDpuede ser exagerado, solo puede configurarlo como corriente absoluta cd -P .. Dudo que funcione en bourneshell, pero debería funcionar en todos los que probó por primera vez. lo hace por mí de todos modos.
mikeserv
@moose ¿Qué sistema operativo está ejecutando?
jlliagre
quien es el alce? ¿Qué?
mikeserv
@mikeserv moose es un transeúnte que publicó un comentario sobre algún problema zshy dirnamerápidamente retiró su comentario ...
jlliagre
Su script Bourne Shell no funcionará con un Bourne Shell ya que Bourne Shell no usa getopt () para cd (1).
schily
4

Asumiendo que realmente se refería a la ruta absoluta, es decir, una ruta desde el directorio raíz:

case $0 in
  /*) mypath=$0;;
  *) mypath=$PWD/$0;;
esac

Esto funciona en cualquier shell de estilo Bourne, por cierto.

Si se refería a una ruta con todos los enlaces simbólicos resueltos, ese es un asunto diferente. readlink -ffunciona en Linux (excluyendo algunos sistemas BusyBox simplificados), FreeBSD, NetBSD, OpenBSD y Cygwin, pero no en OS / X, AIX, HP / UX o Solaris. Si es así readlink, puede llamarlo en un bucle:

realpath () {
  [ -e "$1" ] || return
  case $1 in
    /*) :;;
    *) set "$PWD/$1";;
  esac
  while [ -L "$1" ]; do
    set "${1%/*}" "$(readlink "$1")"
    case $2 in
      /*) set "$2";;
      *) if [ -z "$1" ]; then set "/$2"; else set "$(cd "$1" && pwd -P)/$2"; fi;;
    esac
  done
  case $1 in
    */.|*/..) set "$(cd "$1" && pwd -P)";;
    */./*|*/../*) set "$(cd "${1%/*}" && pwd -P)/${1##*/}"
  esac
  realpath=$1
}

Si no lo tiene readlink, puede aproximarlo ls -n, pero esto solo funciona si lsno altera ningún carácter no imprimible en el nombre del archivo.

poor_mans_readlink () {
  if [ -L "$1" ]; then
    set -- "$1" "$(LC_ALL=C command ls -n -- "$2"; echo z)"
    set -- "${2%??}"
    set -- "${2#*"$1 -> "}"
  fi
  printf '%s\n' "$1"
}

(El extra zes en caso de que el destino del enlace termine en una nueva línea, cuya sustitución de comando de otra manera se consumiría. La realpathfunción no maneja ese caso para los nombres de directorio, por cierto).

Gilles 'SO- deja de ser malvado'
fuente
1
¿Conoce alguna lsimplementación que manipule caracteres no imprimibles cuando la salida no vaya a una terminal?
Stéphane Chazelas
1
@ StéphaneChazelas touch Stéphane; LC_ALL=C busybox ls Stéphane | catSt??phane(eso si el nombre está en UTF-8, latin1 te da un sencillo ?). Creo que también lo he visto en Unices comerciales más antiguos.
Gilles 'SO- deja de ser malvado'
@ StéphaneChazelas He corregido varios errores pero no los he probado exhaustivamente. Avíseme si todavía falla en algunos casos (aparte de la falta de permisos de ejecución en algunos directorios, no voy a tratar ese caso límite).
Gilles 'SO- deja de ser malvado'
@Gilles: ¿qué busyboxes esto? según git busybox ls no ha habido un cambio de código desde 2011. Mi busybox ls- circa 2013 - no hace esto. Este uno - alrededor de 2012 - hace . Esto podría explicar por qué. ¿Ha creado su busyboxsoporte Unicode para incluir el soporte wchar? Es posible que desee probarlo, de lo contrario, verifique las opciones de compilación en el mkinitcpio busyboxpaquete.
mikeserv
Gilles, creo que inicialmente calculé mal esta respuesta, o al menos una parte de ella. Si bien creo firmemente que su mantra de nombres de archivo es una falacia absoluta, definitivamente su poor_mans_readlink está muy bien hecho. Si me haces la amabilidad de hacer una edición, cualquier edición servirá, y hacerme un ping después, me gustaría revertir mi voto sobre esto.
mikeserv
1

Siempre que tenga permisos de ejecución en el directorio actual, o en el directorio desde el que ejecutó su script de shell, si desea una ruta absoluta a un directorio, todo lo que necesita es cd.

Paso 10 de cd las especificaciones

Si la -Popción está vigente, la $PWDvariable de entorno se establecerá en la cadena que generaría pwd -P. Si no hay permiso suficiente en el nuevo directorio, o en cualquier padre de ese directorio, para determinar el directorio de trabajo actual, el valor de$PWD variable de entorno no se especifica.

Y en pwd -P

El nombre de ruta escrito en la salida estándar no debe contener ningún componente que haga referencia a archivos de tipo enlace simbólico. Si hay varios nombres de ruta que elpwd utilidad podría escribir en la salida estándar, uno que comience con un solo carácter / barra diagonal y uno o más que comiencen con dos caracteres / barra diagonal, entonces escribirá el nombre de ruta que comienza con un solo carácter / barra diagonal. El nombre de ruta no debe contener caracteres innecesarios / barra inclinada después de uno o dos caracteres principales / barra diagonal.

Es porque cd -Ptiene que establecer el directorio de trabajo actual a lo que pwd -Pdebería imprimirse y eso cd -tiene que imprimir lo $OLDPWDque funciona:

mkdir ./dir
ln -s ./dir ./ln
cd ./ln ; cd . ; cd -

SALIDA

/home/mikeserv/test/ln

esperalo ...

cd -P . ; cd . ; cd -

SALIDA

/home/mikeserv/test/dir

Y cuando imprimo con cd -estoy imprimiendo $OLDPWD. cdestablece $PWDtan pronto como cd -P . $PWDahora sea una ruta absoluta a /, por lo que no necesito ninguna otra variable. Y en realidad, ni siquiera debería necesitar el seguimiento, .pero hay un comportamiento específico de restablecer $PWDa$HOME en un shell interactivo cuando cdes sin adornos. Por lo tanto, es un buen hábito desarrollar.

Entonces, solo hacer lo anterior en la ruta ${0%/*}debería ser más que suficiente para verificar $0la ruta, pero en el caso de que$0 sea ​​un enlace suave, probablemente no pueda cambiar el directorio en él, desafortunadamente.

Aquí hay una función que manejará eso:

zpath() { cd -P . || return
    _out() { printf "%s$_zdlm\n" "$PWD/${1##*/}"; }
    _cd()  { cd -P "$1" ; } >/dev/null 2>&1
    while [ $# -gt 0 ] && _cd .
    do  if     _cd "$1" 
        then   _out
        elif ! [ -L "$1" ] && [ -e "$1" ] 
        then   _cd "${1%/*}"; _out "$1"
        elif   [ -L "$1" ]
        then   ( while set -- "${1%?/}"; _cd "${1%/*}"; [ -L "${1##*/}" ]
                 do    set " $1" "$(_cd -; ls -nd -- "$1"; echo /)"
                       set -- "${2#*"$1" -> }"
                 done; _out "$1" 
    );  else   ( PS4=ERR:\ NO_SUCH_PATH; set -x; : "$1" )
    fi; _cd -; shift; done
    unset -f _out _cd; unset -v _zdlm                                    
}

Se esfuerza por hacer todo lo que podría en el shell actual, sin invocar un subshell, aunque hay subshells invocados por errores y enlaces blandos que no apuntan a directorios. Depende de un shell compatible con POSIX y de un compatible con POSIX ls, así como de un dispositivo limpio_function() espacio de nombres . Todavía funcionará bien sin este último, aunque puede sobrescribir unsetalgunas funciones de shell actuales en ese caso. En general, todas estas dependencias deberían estar disponibles de manera bastante confiable en una máquina Unix.

Llamado con o sin argumentos, lo primero que hace es restablecer $PWDsu valor canónico: resuelve los enlaces a sus objetivos según sea necesario. Llamado sin argumentos y eso es todo; pero llamó con ellos y resolverá y canonizará la ruta para cada uno o imprimirá un mensaje parastderr qué no.

Debido a que opera principalmente en el shell actual, debería poder manejar una lista de argumentos de cualquier longitud. También busca la $_zdlmvariable (que también unsetes cuando está terminada) e imprime su valor de C-escape inmediatamente a la derecha de cada uno de sus argumentos, cada uno de los cuales siempre va seguido de un solo \ncarácter de línea de hilo.

Cambia mucho el directorio, pero, aparte de establecerlo en su valor canónico, no afecta $PWD, aunque $OLDPWDno se puede contar de ninguna manera cuando está terminado.

Intenta abandonar cada uno de sus argumentos lo antes posible. Primero intenta cdentrar $1. Si puede, imprime la ruta canónica del argumento a stdout. Si no puede, verifica si $1existe y no es un enlace suave. Si es cierto, se imprime.

De esta manera, maneja cualquier argumento de tipo de archivo que el shell tenga permisos para abordar, a menos que $1sea ​​un enlace simbólico que no apunte a un directorio. En ese caso, llama al whilebucle en una subshell.

Llama lspara leer el enlace. El directorio actual debe cambiarse a su valor inicial primero para poder manejar de manera confiable cualquier ruta de referencia y, por lo tanto, en la subshell de sustitución de comandos, la función hace:

cd -...ls...echo /

Se despoja de la izquierda de lsla salida de tan poco como debe contener completamente el nombre del enlace y la cadena ->. Si bien al principio intenté evitar hacer esto shifty $IFSresulta que este es el método más confiable tan cerca como puedo imaginar. Esto es lo mismo que hace poor_mans_readlink de Gilles, y está bien hecho.

Repetirá este proceso en un bucle hasta que el nombre de archivo devuelto lsdefinitivamente no sea un enlace suave. En ese punto, canonicaliza ese camino como antes con las cdimpresiones.

Ejemplo de uso:

zpath \
    /tmp/script \   #symlink to $HOME/test/dir/script.sh
    ln \            #symlink to ./dir/
    ln/nl \         #symlink to ../..
    /dev/fd/0 \     #currently a here-document like : dash <<\HD
    /dev/fd/1 \     #(zlink) | dash
    file \          #regular file                                             
    doesntexist \   #doesnt exist
    /dev/disk/by-path/pci-0000:00:16.2-usb-0:3:1.0-scsi-0:0:0:0 \
    /dev/./././././././null \
    . ..      

SALIDA

/home/mikeserv/test/dir/script.sh
/home/mikeserv/test/dir/
/home/mikeserv/test/
/tmp/zshtpKRVx (deleted)
/proc/17420/fd/pipe:[1782312]
/home/mikeserv/test/file
ERR: NO_SUCH_PATH: doesntexist
/dev/sdd
/dev/null
/home/mikeserv/test/
/home/mikeserv/

O posiblemente ...

ls
dir/  file  file?  folder/  link@  ln@  script*  script3@  script4@

zdlm=\\0 zpath * | cat -A

SALIDA

/home/mikeserv/test/dir/^@$               
/home/mikeserv/test/file^@$
/home/mikeserv/test/file$
^@$
/home/mikeserv/test/folder/^@$
/home/mikeserv/test/file$               #'link' -> 'file\n'
^@$
/home/mikeserv/test/dir/^@$             #'ln' -> './dir'
/home/mikeserv/test/script^@$
/home/mikeserv/test/dir/script.sh^@$    #'script3' -> './dir/script.sh'
/home/mikeserv/test/dir/script.sh^@$    #'script4' -> '/tmp/script' -> ...
mikeserv
fuente
0

¿Qué pasa con una línea simpática cuando hay Python disponible para evitar la redefinición de un algoritmo?

function readlink { python -c "import os.path; print os.path.realpath('$1')"; }

igual que https://stackoverflow.com/a/7305217

Antonio Daniel Rodriguez
fuente