¿Cómo se normaliza una ruta de archivo en Bash?

204

Quiero transformarme /foo/bar/..a/foo

¿Hay un comando bash que hace esto?


Editar: en mi caso práctico, el directorio existe.

Fabien
fuente
44
¿Importa si existe /foo/baro incluso /fooexiste, o solo le interesa el aspecto de manipulación de cadenas de acuerdo con las reglas de nombre de ruta?
Chen Levy el
55
@twalberg ... eso es un poco artificial ...
Camilo Martin
2
No @CamiloMartin ideó en absoluto - que hace exactamente lo que hace la pregunta - transforma /foo/bar/..a /foo, y el uso de un bashcomando. Si hay otros requisitos que no están establecidos, entonces quizás deberían ser ...
twalberg
14
@twalberg Has estado haciendo demasiado TDD -_- '
Camilo Martin

Respuestas:

193

si desea cortar parte de un nombre de archivo de la ruta, "dirname" y "basename" son sus amigos, y "realpath" también es útil.

dirname /foo/bar/baz 
# /foo/bar 
basename /foo/bar/baz
# baz
dirname $( dirname  /foo/bar/baz  ) 
# /foo 
realpath ../foo
# ../foo: No such file or directory
realpath /tmp/../tmp/../tmp
# /tmp

realpath alternativas

Si realpathsu shell no lo admite, puede intentar

readlink -f /path/here/.. 

también

readlink -m /path/there/../../ 

Funciona igual que

realpath -s /path/here/../../

en que el camino no necesita existir para ser normalizado.

Kent Fredric
fuente
55
Para aquellos de ustedes que necesitan una solución OS X, consulte la respuesta de Adam Liss a continuación.
Trenton
stackoverflow.com/a/17744637/999943 ¡ Esta es una respuesta muy relacionada! Me encontré con estas dos publicaciones de control de calidad en un día, y quería vincularlas.
phyatt
Parece que realpath se agregó a coreutils en 2012. Consulte el historial de archivos en github.com/coreutils/coreutils/commits/master/src/realpath.c .
Noah Lavine
1
Ambos realpathy readlinkprovienen de las utilidades principales de GNU, por lo que lo más probable es que obtenga ambos o ninguno. Si no recuerdo mal, la versión de readlink en Mac es ligeramente diferente a la de GNU: - \
Antoine 'hashar' Musso
100

No sé si hay un comando bash directo para hacer esto, pero generalmente lo hago

normalDir="`cd "${dirToNormalize}";pwd`"
echo "${normalDir}"

y funciona bien

Tim Whitcomb
fuente
9
Esto normalizará pero no resolverá los enlaces blandos. Esto puede ser un error o una característica. :-)
Adam Liss
44
También tiene un problema si se define $ CDPATH; porque el "cd foo" cambiará a cualquier directorio "foo" que sea un subdirectorio de $ CDPATH, no solo un "foo" que esté en el directorio actual. Creo que debe hacer algo como: CDPATH = "" cd "$ {dirToNormalize}" && pwd -P.
mjs
77
La respuesta de Tim es definitivamente la más simple y portátil. CDPATH es fácil de manejar: dir = "$ (CDPATH no establecido && cd" $ dir "&& pwd)"
David Blevins
2
¡Esto podría ser muy peligroso ( rm -rf $normalDir) si dirToNormalize no existe!
Frank Meulenaar
44
Sí, probablemente sea mejor usar un &&según el comentario de @ DavidBlevins.
Elias Dorneles
54

Tratar realpath. A continuación se muestra la fuente en su totalidad, por la presente donada al dominio público.

// realpath.c: display the absolute path to a file or directory.
// Adam Liss, August, 2007
// This program is provided "as-is" to the public domain, without express or
// implied warranty, for any non-profit use, provided this notice is maintained.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libgen.h>   
#include <limits.h>

static char *s_pMyName;
void usage(void);

int main(int argc, char *argv[])
{
    char
        sPath[PATH_MAX];


    s_pMyName = strdup(basename(argv[0]));

    if (argc < 2)
        usage();

    printf("%s\n", realpath(argv[1], sPath));
    return 0;
}    

void usage(void)
{
    fprintf(stderr, "usage: %s PATH\n", s_pMyName);
    exit(1);
}
Adam Liss
fuente
1
¿Se incluye esto como parte de la instalación estándar de bash? Recibo "comando no encontrado" en nuestro sistema (bash 3.00.15 (1) en RHEL)
Tim Whitcomb
44
gnu.org/software/coreutils para readlink, pero realpath proviene de este paquete: packages.debian.org/unstable/utils/realpath
Kent Fredric
8
Es estándar en ubuntu / BSD, no Centos / OSX
Erik Aronesty
44
Realmente deberías atravesar la parte con "Bonus: es un comando bash, ¡y está disponible en todas partes!" parte sobre realpath. También es mi favorito, pero en lugar de compilar su herramienta desde la fuente. Todo es para peso pesado, y la mayoría de las veces solo quieres readlink -f... Por cierto, readlinktampoco es un bash incorporado, sino parte de coreutilsUbuntu.
Tomasz Gandor
44
Usted ha dicho que está donando esto al dominio público, pero el encabezado también dice "para cualquier uso sin fines de lucro", ¿realmente quiere donarlo al dominio público? Eso significaría que puede usarse con fines comerciales con fines de lucro, así como sin fines de lucro ...
yo_y
36

Una solución portátil y confiable es usar python, que está preinstalado prácticamente en todas partes (incluido Darwin). Tienes dos opciones:

  1. abspath devuelve una ruta absoluta pero no resuelve enlaces simbólicos:

    python -c "import os,sys; print(os.path.abspath(sys.argv[1]))" path/to/file

  2. realpath devuelve una ruta absoluta y al hacerlo resuelve enlaces simbólicos, generando una ruta canónica:

    python -c "import os,sys; print(os.path.realpath(sys.argv[1]))" path/to/file

En cada caso, path/to/filepuede ser una ruta relativa o absoluta.

loevborg
fuente
77
Gracias, ese fue el único que funcionó. readlink o realpath no están disponibles en OS X. Python debería estar en la mayoría de las plataformas.
sorin
1
Solo para aclarar, readlinkestá disponible en OS X, solo que no con la -fopción. Soluciones portátiles discutidas aquí .
StvnW
3
Literalmente me sorprende que esta sea la única solución sensata si no desea seguir los enlaces. La manera de Unix mi PIE.
DannoHung
34

Use la utilidad readlink del paquete coreutils.

MY_PATH=$(readlink -f "$0")
mattalxndr
fuente
1
BSD no tiene el indicador -f, lo que significa que esto fallará incluso en el último MacOS Mojave y muchos otros sistemas. Olvídate de usar -f si quieres portabilidad, muchos sistemas operativos se ven afectados.
sorin
1
@sorin. La pregunta no es sobre Mac, sino sobre Linux.
mattalxndr
14

readlinkes el estándar bash para obtener la ruta absoluta. También tiene la ventaja de devolver cadenas vacías si las rutas o una ruta no existe (dadas las banderas para hacerlo).

Para obtener la ruta absoluta a un directorio que puede existir o no, pero los padres de quién existen, use:

abspath=$(readlink -f $path)

Para obtener la ruta absoluta a un directorio que debe existir junto con todos los padres:

abspath=$(readlink -e $path)

Para canonizar la ruta dada y seguir enlaces simbólicos si existen, pero ignorar los directorios que faltan y simplemente devolver la ruta de todos modos, es:

abspath=$(readlink -m $path)

El único inconveniente es que readlink seguirá a los enlaces. Si no desea seguir los enlaces, puede usar esta convención alternativa:

abspath=$(cd ${path%/*} && echo $PWD/${path##*/})

Eso conducirá a la parte del directorio de $ path e imprimirá el directorio actual junto con la parte del archivo de $ path. Si no puede chdir, obtienes una cadena vacía y un error en stderr.

Craig
fuente
77
readlinkEs una buena opción si está disponible. La versión de OS X no admite las opciones -eo -f. En los primeros tres ejemplos, debe tener comillas dobles $pathpara manejar espacios o comodines en el nombre del archivo. +1 para la expansión de parámetros, pero esto tiene una vulnerabilidad de seguridad. Si pathestá vacío, esto lo hará cda su directorio de inicio. Necesitas comillas dobles. abspath=$(cd "${path%/*}" && echo "$PWD/${path##*/}")
toxalot
Esto fue solo un ejemplo. Si estás empeñado en la seguridad, entonces realmente no deberías usar bash ni ninguna otra variante de shell. Además, bash tiene sus propios problemas cuando se trata de compatibilidad multiplataforma, además de tener problemas con el cambio de funcionalidad entre las principales versiones. OSX es solo una de las muchas plataformas con problemas relacionados con las secuencias de comandos de shell, sin mencionar que se basa en BSD. Cuando tiene que ser verdaderamente multiplataforma, debe ser compatible con POSIX, por lo que la expansión de parámetros realmente desaparece. Eche un vistazo a Solaris o HP-UX en algún momento.
Craig
66
No significa ningún delito aquí, pero es importante señalar problemas oscuros como este. Solo quiero una respuesta rápida a este problema trivial y hubiera confiado en ese código con cualquier / toda entrada si no fuera por el comentario anterior. También es importante admitir OS-X en estas discusiones bash. Desafortunadamente, hay muchos comandos que no son compatibles con OS-X, y muchos foros dan eso por sentado cuando se habla de Bash, lo que significa que continuaremos recibiendo muchos problemas multiplataforma a menos que se aborde más temprano que tarde.
Rebs
13

Antigua pregunta, pero hay una forma mucho más simple si se trata de nombres de ruta completos a nivel de shell:

   abspath = "$ (cd" $ ruta "&& pwd)"

Como el cd ocurre en una subshell, no afecta el script principal.

Dos variaciones, suponiendo que sus comandos integrados de shell acepten -L y -P, son:

   abspath = "$ (cd -P" $ ruta "&& pwd -P)" # ruta física con enlaces simbólicos resueltos
   abspath = "$ (cd -L" $ ruta "&& pwd -L)" # ruta lógica conservando enlaces simbólicos

Personalmente, rara vez necesito este enfoque posterior a menos que me fascinen los enlaces simbólicos por alguna razón.

FYI: variación en la obtención del directorio de inicio de un script que funciona incluso si el script cambia su directorio actual más adelante.

name0 = "$ (nombre base" $ 0 ")"; #base name of script
dir0 = "$ (cd" $ (dirname "$ 0") "&& pwd)"; #absolute dir inicio

El uso de CD asegura que siempre tenga el directorio absoluto, incluso si el script se ejecuta mediante comandos como ./script.sh que, sin el cd / pwd, a menudo da solo ... Inútil si el script hace un cd más adelante.

Gilbert
fuente
8

Como señaló Adam Liss, realpathno se incluye en todas las distribuciones. Lo cual es una pena, porque es la mejor solución. El código fuente proporcionado es excelente, y probablemente comenzaré a usarlo ahora. Esto es lo que he estado usando hasta ahora, que comparto aquí solo para completar:

get_abs_path() {
     local PARENT_DIR=$(dirname "$1")
     cd "$PARENT_DIR"
     local ABS_PATH="$(pwd)"/"$(basename "$1")"
     cd - >/dev/null
     echo "$ABS_PATH"
} 

Si usted quiere que los enlaces simbólicos a resolver, simplemente reemplazar pwdcon pwd -P.

Jeet
fuente
Uno tiene la pwd -Popción para este caso ... Considere lo que sucedería si $(basename "$1")fuera un enlace simbólico a un archivo en otro directorio. El pwd -Púnico resuelve enlaces simbólicos en la parte del directorio de la ruta, pero no la parte del nombre base.
toxalot
7

Mi solución reciente fue:

pushd foo/bar/..
dir=`pwd`
popd

Basado en la respuesta de Tim Whitcomb.

Schmunk
fuente
Sospecho que esto falla si el argumento no es un directorio. Supongamos que quiero saber a dónde / usr / bin / java conduce?
Edward Falk
1
Si sabe que es un archivo, puede pushd $(dirname /usr/bin/java)intentarlo.
schmunk
5

No es exactamente una respuesta, sino quizás una pregunta de seguimiento (la pregunta original no era explícita):

readlinkestá bien si realmente quieres seguir enlaces simbólicos. Pero también hay un caso de uso por el solo hecho de la normalización ./y ../y //secuencias, que se pueden hacer puramente sintáctico, sin canonizan enlaces simbólicos. readlinkno es bueno para esto, y tampoco lo es realpath.

for f in $paths; do (cd $f; pwd); done

funciona para caminos existentes, pero se rompe para otros.

Una sedsecuencia de comandos parecería ser una buena apuesta, excepto que no puede reemplazar iterativamente secuencias ( /foo/bar/baz/../..-> /foo/bar/..-> /foo) sin usar algo como Perl, que no es seguro asumir en todos los sistemas, o usar algún bucle feo para comparar la salida de seda su entrada

FWIW, una línea usando Java (JDK 6+):

jrunscript -e 'for (var i = 0; i < arguments.length; i++) {println(new java.io.File(new java.io.File(arguments[i]).toURI().normalize()))}' $paths
Jesse Glick
fuente
realpathtiene una -sopción para no resuelve los enlaces simbólicos y únicas referencias a resolver /./, /../y retire adicionales /caracteres. Cuando se combina con la -mopción, realpathfunciona solo con el nombre del archivo y no toca ningún archivo real. Que suena como la solución perfecta. Pero, por desgracia, realpathtodavía falta en muchos sistemas.
toxalot
La eliminación de ..componentes no se puede hacer sintácticamente cuando los enlaces simbólicos están involucrados. /one/two/../threeno es lo mismo que /one/threesi fuera twoun enlace simbólico a /foo/bar.
jrw32982 es compatible con Monica el
@ jrw32982 sí, como dije en mi respuesta, esto es para un caso de uso cuando la canonicalización de enlaces simbólicos no es deseada o necesaria.
Jesse Glick
@JesseGlick no se trata solo de si quieres o no canonizar enlaces simbólicos. Su algoritmo en realidad produce la respuesta incorrecta. Para que su respuesta sea correcta, debe saber a priori que no había enlaces simbólicos involucrados (o que solo eran de cierta forma). Su respuesta dice que no desea canonizarlos, no que no haya enlaces simbólicos involucrados en la ruta.
jrw32982 es compatible con Monica el
Hay casos de uso en los que se debe realizar la normalización sin asumir ninguna estructura de directorio fija y existente. La normalización de URI es similar. En estos casos, es una limitación inherente que, en general, el resultado no será correcto si hay enlaces simbólicos cerca de un directorio donde el resultado se aplica más tarde.
Jesse Glick
5

Llego tarde a la fiesta, pero esta es la solución que he creado después de leer un montón de hilos como este:

resolve_dir() {
        (builtin cd `dirname "${1/#~/$HOME}"`'/'`basename "${1/#~/$HOME}"` 2>/dev/null; if [ $? -eq 0 ]; then pwd; fi)
}

Esto resolverá la ruta absoluta de $ 1, jugará bien con ~, mantendrá los enlaces simbólicos en la ruta donde están, y no interferirá con su pila de directorios. Devuelve la ruta completa o nada si no existe. Espera que $ 1 sea un directorio y probablemente fallará si no lo es, pero esa es una verificación fácil de hacer usted mismo.

apottere
fuente
4

Hablador, y un poco tarde la respuesta. Necesito escribir uno ya que estoy atascado en RHEL4 / 5 anterior. Manejo los enlaces absolutos y relativos, y simplifica las entradas //, /./ y somedir /../.

test -x /usr/bin/readlink || readlink () {
        echo $(/bin/ls -l $1 | /bin/cut -d'>' -f 2)
    }


test -x /usr/bin/realpath || realpath () {
    local PATH=/bin:/usr/bin
    local inputpath=$1
    local changemade=1
    while [ $changemade -ne 0 ]
    do
        changemade=0
        local realpath=""
        local token=
        for token in ${inputpath//\// }
        do 
            case $token in
            ""|".") # noop
                ;;
            "..") # up one directory
                changemade=1
                realpath=$(dirname $realpath)
                ;;
            *)
                if [ -h $realpath/$token ] 
                then
                    changemade=1
                    target=`readlink $realpath/$token`
                    if [ "${target:0:1}" = '/' ]
                    then
                        realpath=$target
                    else
                        realpath="$realpath/$target"
                    fi
                else
                    realpath="$realpath/$token"
                fi
                ;;
            esac
        done
        inputpath=$realpath
    done
    echo $realpath
}

mkdir -p /tmp/bar
(cd /tmp ; ln -s /tmp/bar foo; ln -s ../.././usr /tmp/bar/link2usr)
echo `realpath /tmp/foo`
Alhernau
fuente
3

Pruebe nuestro nuevo producto de biblioteca Bash realpath-lib que hemos colocado en GitHub para uso gratuito y sin gravámenes. Está completamente documentado y es una gran herramienta de aprendizaje.

Resuelve rutas locales, relativas y absolutas y no tiene ninguna dependencia, excepto Bash 4+; por lo que debería funcionar en casi cualquier lugar. Es gratis, limpio, simple e instructivo.

Tu puedes hacer:

get_realpath <absolute|relative|symlink|local file path>

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

}

También contiene funciones para get_dirname, get_filename, get_ stemname y validate_path. Pruébelo en todas las plataformas y ayude a mejorarlo.

AsymLabs
fuente
2

El problema realpathes que no está disponible en BSD (u OSX para el caso). Aquí hay una receta simple extraída de un artículo bastante antiguo (2009) de Linux Journal , que es bastante portátil:

function normpath() {
  # Remove all /./ sequences.
  local path=${1//\/.\//\/}

  # Remove dir/.. sequences.
  while [[ $path =~ ([^/][^/]*/\.\./) ]]; do
    path=${path/${BASH_REMATCH[0]}/}
  done
  echo $path
}

Observe esta variante tampoco no requiere la trayectoria de existir.

André Anjos
fuente
Sin embargo, esto no resuelve los enlaces simbólicos. Realpath procesa rutas que comienzan en la raíz y sigue enlaces simbólicos a medida que avanza. Todo lo que hace es colapsar las referencias principales.
Martijn Pieters
2

Según la respuesta de @ Andre, podría tener una versión un poco mejor, en caso de que alguien busque una solución basada en la manipulación de cadenas completamente libre de bucles. También es útil para aquellos que no desean desreferenciar ningún enlace simbólico, que es la desventaja de usar realpatho readlink -f.

Funciona en versiones bash 3.2.25 y superiores.

shopt -s extglob

normalise_path() {
    local path="$1"
    # get rid of /../ example: /one/../two to /two
    path="${path//\/*([!\/])\/\.\./}"
    # get rid of /./ and //* example: /one/.///two to /one/two
    path="${path//@(\/\.\/|\/+(\/))//}"
    # remove the last '/.'
    echo "${path%%/.}"
}

$ normalise_path /home/codemedic/../codemedic////.config
/home/codemedic/.config
ϹοδεMεδιϲ
fuente
Esta es una buena idea, pero perdí 20 minutos tratando de hacer que esto funcione en varias versiones diferentes de bash. Resulta que la opción de shell extglob necesita estar activada para que esto funcione, y no es por defecto. Cuando se trata de la funcionalidad bash, es importante especificar tanto la versión requerida como las opciones no predeterminadas porque estos detalles pueden variar entre los sistemas operativos. Por ejemplo, una versión reciente de Mac OSX (Yosemite) solo viene con una versión desactualizada de bash (3.2).
drwatsoncode
Lo siento @ricovox; Ahora los he actualizado. Estoy ansioso por saber la versión exacta de Bash que tienes allí. La fórmula anterior (actualizada) funciona en CentOS 5.8 que viene con bash 3.2.25
ϹοδεMεδιϲ
Perdón por la confusion. Este código funcionó en mi versión de Mac OSX bash (3.2.57) una vez que encendí extglob. Mi nota sobre las versiones de bash fue más general (que en realidad se aplica más a otra respuesta aquí con respecto a la expresión regular en bash).
drwatsoncode
2
Aunque aprecio tu respuesta. Lo usé como base para los míos. Por cierto, noté varios casos en los que el suyo falla: (1) Trayectos relativos hello/../world(2) Puntos en el nombre del archivo /hello/..world(3) Puntos después de la doble barra /hello//../world(4) Punto antes o después de la doble barra /hello//./worldo /hello/.//world (5) Principal después de la actual: /hello/./../world/ (6) Principal después de Parent:, /hello/../../worldetc. - Algunos de estos pueden repararse mediante el uso de un bucle para hacer correcciones hasta que la ruta deje de cambiar. (También elimine dir/../, /dir/..pero no elimine dir/..del final.)
drwatsoncode
0

Basado en el excelente fragmento de python de loveborg, escribí esto:

#!/bin/sh

# Version of readlink that follows links to the end; good for Mac OS X

for file in "$@"; do
  while [ -h "$file" ]; do
    l=`readlink $file`
    case "$l" in
      /*) file="$l";;
      *) file=`dirname "$file"`/"$l"
    esac
  done
  #echo $file
  python -c "import os,sys; print os.path.abspath(sys.argv[1])" "$file"
done
Edward Falk
fuente
0
FILEPATH="file.txt"
echo $(realpath $(dirname $FILEPATH))/$(basename $FILEPATH)

Esto funciona incluso si el archivo no existe. Requiere que exista el directorio que contiene el archivo.

usuario240515
fuente
GNU Realpath tampoco requiere que exista el último elemento en la ruta, a menos que userealpath -e
Martijn Pieters
0

Necesitaba una solución que hiciera las tres cosas:

  • Trabaja en una Mac de serie. realpathy readlink -fson complementos
  • Resolver enlaces simbólicos
  • Tiene manejo de errores

Ninguna de las respuestas tenía tanto # 1 como # 2. Agregué el n. ° 3 para salvar a los demás.

#!/bin/bash

P="${1?Specify a file path}"

[ -e "$P" ] || { echo "File does not exist: $P"; exit 1; }

while [ -h "$P" ] ; do
    ls="$(ls -ld "$P")"
    link="$(expr "$ls" : '.*-> \(.*\)$')"
    expr "$link" : '/.*' > /dev/null &&
        P="$link" ||
        P="$(dirname "$P")/$link"
done
echo "$(cd "$(dirname "$P")"; pwd)/$(basename "$P")"

Aquí hay un breve caso de prueba con algunos espacios retorcidos en las rutas para ejercer completamente la cita

mkdir -p "/tmp/test/ first path "
mkdir -p "/tmp/test/ second path "
echo "hello" > "/tmp/test/ first path / red .txt "
ln -s "/tmp/test/ first path / red .txt " "/tmp/test/ second path / green .txt "

cd  "/tmp/test/ second path "
fullpath " green .txt "
cat " green .txt "
David Blevins
fuente
0

Sé que esta es una pregunta antigua. Todavía estoy ofreciendo una alternativa. Recientemente me encontré con el mismo problema y no encontré ningún comando portátil y existente para hacerlo. Así que escribí el siguiente script de shell que incluye una función que puede hacer el truco.

#! /bin/sh                                                                                                                                                

function normalize {
  local rc=0
  local ret

  if [ $# -gt 0 ] ; then
    # invalid
    if [ "x`echo $1 | grep -E '^/\.\.'`" != "x" ] ; then
      echo $1
      return -1
    fi

    # convert to absolute path
    if [ "x`echo $1 | grep -E '^\/'`" == "x" ] ; then
      normalize "`pwd`/$1"
      return $?
    fi

    ret=`echo $1 | sed 's;/\.\($\|/\);/;g' | sed 's;/[^/]*[^/.]\+[^/]*/\.\.\($\|/\);/;g'`
  else
    read line
    normalize "$line"
    return $?
  fi

  if [ "x`echo $ret | grep -E '/\.\.?(/|$)'`" != "x" ] ; then
    ret=`normalize "$ret"`
    rc=$?
  fi

  echo "$ret"
  return $rc
}

https://gist.github.com/bestofsong/8830bdf3e5eb9461d27313c3c282868c

bestOfSong
fuente
0

Hice una función integrada solo para manejar esto con un enfoque en el rendimiento más alto posible (por diversión). No resuelve enlaces simbólicos, por lo que es básicamente lo mismo que realpath -sm.

## A bash-only mimic of `realpath -sm`. 
## Give it path[s] as argument[s] and it will convert them to clean absolute paths
abspath () { 
  ${*+false} && { >&2 echo $FUNCNAME: missing operand; return 1; };
  local c s p IFS='/';  ## path chunk, absolute path, input path, IFS for splitting paths into chunks
  local -i r=0;         ## return value

  for p in "$@"; do
    case "$p" in        ## Check for leading backslashes, identify relative/absolute path
    '') ((r|=1)); continue;;
    //[!/]*)  >&2 echo "paths =~ ^//[^/]* are impl-defined; not my problem"; ((r|=2)); continue;;
    /*) ;;
    *)  p="$PWD/$p";;   ## Prepend the current directory to form an absolute path
    esac

    s='';
    for c in $p; do     ## Let IFS split the path at '/'s
      case $c in        ### NOTE: IFS is '/'; so no quotes needed here
      ''|.) ;;          ## Skip duplicate '/'s and '/./'s
      ..) s="${s%/*}";; ## Trim the previous addition to the absolute path string
      *)  s+=/$c;;      ### NOTE: No quotes here intentionally. They make no difference, it seems
      esac;
    done;

    echo "${s:-/}";     ## If xpg_echo is set, use `echo -E` or `printf $'%s\n'` instead
  done
  return $r;
}

Nota: Esta función no maneja rutas que comiencen //, ya que exactamente dos barras diagonales dobles al comienzo de una ruta son comportamientos definidos por la implementación. Sin embargo, se maneja /, ///y así sucesivamente.

Esta función parece manejar todos los casos límite correctamente, pero aún puede haber algunos por ahí que no he tratado.

Nota de rendimiento: cuando se llama con miles de argumentos, se abspathejecuta aproximadamente 10 veces más lento que realpath -sm; cuando se llama con un solo argumento, se abspathejecuta> 110 veces más rápido que realpath -smen mi máquina, principalmente debido a que no es necesario ejecutar un nuevo programa cada vez.

Jeffrey Cash
fuente
-1

Hoy descubrí que puedes usar el statcomando para resolver rutas.

Entonces, para un directorio como "~ / Documentos":

Puedes ejecutar esto:

stat -f %N ~/Documents

Para obtener el camino completo:

/Users/me/Documents

Para enlaces simbólicos, puede usar la opción de formato% Y:

stat -f %Y example_symlink

Lo que podría devolver un resultado como:

/usr/local/sbin/example_symlink

Las opciones de formato pueden ser diferentes en otras versiones de * NIX, pero me han funcionado en OSX.

Coldlogic
fuente
1
la stat -f %N ~/Documentslínea es un arenque rojo ... su caparazón se está reemplazando ~/Documentspor /Users/me/Documents, y statsolo está imprimiendo su argumento literalmente.
danwyand
-4

Una solución simple usando node.js:

#!/usr/bin/env node
process.stdout.write(require('path').resolve(process.argv[2]));
Artesano72
fuente