¿Cómo normalizar una ruta de archivo en Bash?


Quiero transformar /foo/bar/.. a /foo

¿Hay un comando bash que hace esto?


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

Author: Fabien, 2008-11-12

21 answers

Si quieres chomp parte de un nombre de archivo de la ruta, "dirname" y "basename" son tus 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 realpath no es compatible con su shell, puede probar

readlink -f /path/here/.. 

También

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

Funciona igual que

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

En que la ruta no necesita existir para ser normalizada.

 156
Author: Kent Fredric,
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
2018-08-07 21:38:49

No se si hay una orden directa de bash para hacer esto, pero usualmente lo hago

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

Y funciona bien.

 82
Author: Tim Whitcomb,
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-07-18 04:33:04

Intenta realpath. A continuación se muestra la fuente en su totalidad, 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);
}
 53
Author: Adam Liss,
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-08-07 13:58:49

Utilice la utilidad readlink del paquete coreutils.

MY_PATH=$(readlink -f "$0")
 34
Author: mattalxndr,
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-08-26 08:32:31

Una solución portátil y confiable es usar python, que está preinstalado en casi 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/file puede ser una ruta relativa o absoluta.

 31
Author: loevborg,
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-01-07 18:37:03

readlink es 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 existen (dadas las banderas para hacerlo).

Para obtener la ruta absoluta a un directorio que puede o no existir, pero cuyos padres 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 de lo contrario ignore los directorios que faltan y simplemente devuelva la ruta de todos modos, es:

abspath=$(readlink -m $path)

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

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

Que chdir a la parte del directorio de path path e imprimir el directorio actual junto con la parte del archivo de path path. Si falla en chdir, se obtiene una cadena vacía y un error en stderr.

 13
Author: Craig,
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-02 22:06:58

Mi solución reciente fue:

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

Basado en la respuesta de Tim Whitcomb.

 7
Author: schmunk,
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-10-10 19:53:55

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

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

Como el cd sucede en una subcapa, no afecta al script principal.

Dos variaciones, suponiendo que los comandos integrados en el shell acepten-L y-P, son:

   abspath="$( cd -P "$path" && pwd -P )"    #physical path with resolved symlinks
   abspath="$( cd -L "$path" && pwd -L )"    #logical path preserving symlinks

Personalmente, rara vez necesito este enfoque posterior a menos que esté fascinado con 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="$(basename "$0")";                  #base name of script
dir0="$( cd "$( dirname "$0" )" && pwd )"; #absolute starting dir

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

 7
Author: Gilbert,
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-07 18:00:05

Como Adam Liss señaló realpath no se incluye en todas las distribuciones. Lo cual es una pena, porque es la mejor solución. El código fuente proporcionado es genial, 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 desea que resuelva los enlaces simbólicos, simplemente reemplace pwd por pwd -P.

 6
Author: Jeet,
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-05-11 07:56:16

Hablador, y un poco tarde respuesta. Necesito escribir uno ya que estoy atascado en RHEL4/5 más viejo. Maneja enlaces absolutos y relativos, y simplifica //,/./ y somedir/../ entrada.

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`
 4
Author: alhernau,
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
2009-11-10 14:33:47

No es exactamente una respuesta, pero tal vez una pregunta de seguimiento (la pregunta original no era explícita):

readlink está bien si realmente quieres seguir enlaces simbólicos. Pero también hay un caso de uso para simplemente normalizar ./ y ../ y // secuencias, que se pueden hacer puramente sintácticamente, sin canonizar enlaces simbólicos. readlink no es bueno para esto, y tampoco lo es realpath.

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

Funciona para rutas existentes, pero se rompe para otras.

Un script sed sería parece ser una buena apuesta, excepto que no puedes reemplazar secuencias iterativamente(/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 sed con su entrada.

FWIW, un one-liner 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
 4
Author: Jesse Glick,
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-05-29 23:16:49

Llego tarde a la fiesta, pero esta es la solución que he elaborado 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á enlaces simbólicos en la ruta donde están, y no se meterá 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 comprobación fácil de hacer usted mismo.

 4
Author: apottere,
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-08-24 16:49:31

Pruebe nuestro nuevo producto de la biblioteca Bash realpath-lib que hemos colocado en GitHub para un uso gratuito y sin trabas. 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 casi en cualquier lugar. Es gratis, limpio, simple e instructivo.

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ébalo en todas las plataformas y ayuda a mejorarlo.

 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
2013-10-03 03:56:54

Basado en la respuesta de @Andre, podría tener una versión ligeramente mejor, en caso de que alguien esté buscando una solución completamente basada en manipulación de cadenas y sin bucles. También es útil para aquellos que no quieren desreferenciar ningún enlace simbólico, que es la desventaja de usar realpath o readlink -f.

Funciona en bash versiones 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
 2
Author: ϹοδεMεδιϲ,
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-10-12 01:44:25

El problema con realpath es que no está disponible en BSD (o 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 que esta variante también hace no requiere que la ruta exista.

 1
Author: André Anjos,
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-03-25 13:10:23

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
 0
Author: Edward Falk,
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-06-24 21:17:39
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.

 0
Author: user240515,
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
2018-02-14 19:04:24

Necesitaba una solución que hiciera las tres cosas:

  • Trabaja en un Mac de stock. realpath y readlink -f son complementos
  • Resolver enlaces simbólicos
  • Tienen manejo de errores

Ninguna de las respuestas tenía tanto #1 como #2. Agregué el # 3 para salvar a otros más afeitándose yak.

#!/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 plenamente 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 "
 0
Author: David Blevins,
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
2018-05-16 01:53:30

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 existente y portátil para hacer eso. 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

 0
Author: bestOfSong,
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
2018-08-18 10:03:23

Hoy descubrí que puedes usar el comando stat para resolver rutas.

Así que para un directorio como "~/Documents":

Puedes ejecutar esto:

stat -f %N ~/Documents

Para obtener el camino completo:

/Users/me/Documents

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

stat -f %Y example_symlink

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 funcionaron para mí en OSX.

 -1
Author: coldlogic,
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-05-30 00:58:06

Una solución simple usando node.js:

#!/usr/bin/env node
process.stdout.write(require('path').resolve(process.argv[2]));
 -3
Author: Artisan72,
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-01-12 22:23:43