Ejemplo:
absolute="/foo/bar"
current="/foo/baz/foo"
# Magic
relative="../../bar"
¿Cómo creo la magia (espero que no sea un código demasiado complicado ...)?
bash
shell
path
relative-path
absolute-path
Paul Tarjan
fuente
fuente
realpath --relative-to=$absolute $current
.Respuestas:
Usar realpath de GNU coreutils 8.23 es el más simple, creo:
Por ejemplo:
fuente
$ realpath --relative-to="${PWD}" "$file"
es útil si desea las rutas relativas al directorio de trabajo actual./usr/bin/nmap/
-path pero no para/usr/bin/nmap
: denmap
a/tmp/testing
es solo../../
y no 3 veces../
. Sin embargo, funciona porque hacerlo..
en rootfs es/
.--relative-to=…
espera un directorio y NO lo verifica. Eso significa que terminará con un "../" adicional si solicita una ruta relativa a un archivo (como parece hacer este ejemplo, porque/usr/bin
rara vez o nunca contiene directorios ynmap
normalmente es un binario)da:
fuente
relpath(){ python -c "import os.path; print os.path.relpath('$1','${2:-$PWD}')" ; }
python -c 'import os, sys; print(os.path.relpath(*sys.argv[1:]))'
funciona de forma más natural y confiable.Esta es una mejora corregida y completamente funcional de la solución actualmente mejor calificada de @pini (que lamentablemente maneja solo unos pocos casos)
Recordatorio: '-z' prueba si la cadena es de longitud cero (= vacía) y '-n' prueba si la cadena no está vacía.
Casos de prueba :
fuente
source=$1; target=$2
consource=$(realpath $1); target=$(realpath $2)
realpath
se recomienda,source=$(readlink -f $1)
etc., si realpath no está disponible (no es estándar)$source
y me$target
gusta esto: `if [[-e $ 1]]; entonces fuente = $ (readlink -f $ 1); otra fuente = $ 1; fi si [[-e $ 2]]; entonces target = $ (readlink -f $ 2); más objetivo = $ 2; fi` De esa manera, la función podría manejar rutas relativas reales / existentes así como directorios ficticios.readlink
tiene una-m
opción que simplemente hace eso;)fuente
Está integrado en Perl desde 2001, por lo que funciona en casi todos los sistemas que puedas imaginar, incluso VMS .
Además, la solución es fácil de entender.
Entonces, para su ejemplo:
... funcionaría bien.
fuente
say
no ha estado disponible en perl como registro, pero podría usarse de manera efectiva aquí.perl -MFile::Spec -E 'say File::Spec->abs2rel(@ARGV)'
perl -MFile::Spec -e 'print File::Spec->abs2rel(@ARGV)' "$target"
yperl -MFile::Spec -e 'print File::Spec->abs2rel(@ARGV)' "$target" "$origin"
. El primer script perl de una línea usa un argumento (el origen es el directorio de trabajo actual). El segundo script perl de una línea usa dos argumentos.perl
se puede encontrar en casi todas partes, aunque la respuesta sigue siendo de una sola línea.Suponiendo que haya instalado: bash, pwd, dirname, echo; entonces relpath es
He jugado la respuesta de pini y algunas otras ideas
Nota : Esto requiere que ambas rutas sean carpetas existentes. Los archivos no funcionarán.
fuente
Python
os.path.relpath
como una función de shellEl objetivo de este
relpath
ejercicio es imitar laos.path.relpath
función de Python 2.7 (disponible desde Python versión 2.6 pero solo funciona correctamente en 2.7), según lo propuesto por xni . Como consecuencia, algunos de los resultados pueden diferir de las funciones proporcionadas en otras respuestas.(No he probado con nuevas líneas en las rutas simplemente porque rompe la validación basada en llamadas
python -c
desde ZSH. Ciertamente sería posible con un poco de esfuerzo).Con respecto a la "magia" en Bash, he dejado de buscar magia en Bash hace mucho tiempo, pero desde entonces he encontrado toda la magia que necesito, y algo más, en ZSH.
En consecuencia, propongo dos implementaciones.
La primera implementación tiene como objetivo ser totalmente compatible con POSIX . Lo he probado con
/bin/dash
Debian 6.0.6 "Squeeze". También funciona perfectamente con/bin/sh
OS X 10.8.3, que en realidad es la versión 3.2 de Bash que finge ser un shell POSIX.La segunda implementación es una función de shell ZSH que es robusta contra múltiples barras y otras molestias en las rutas. Si tiene ZSH disponible, esta es la versión recomendada, incluso si la está llamando en el formulario de script presentado a continuación (es decir, con un shebang de
#!/usr/bin/env zsh
) desde otro shell.Finalmente, he escrito un script ZSH que verifica el resultado del
relpath
comando encontrado en$PATH
los casos de prueba proporcionados en otras respuestas. Agregué algo de sabor a esas pruebas agregando algunos espacios, pestañas y signos de puntuación, como! ? *
aquí y allá, y también agregué otra prueba con exóticos personajes UTF-8 encontrados en vim-powerline .Función de shell POSIX
Primero, la función de shell compatible con POSIX. Funciona con una variedad de rutas, pero no limpia múltiples barras ni resuelve enlaces simbólicos.
Función de shell ZSH
Ahora, la
zsh
versión más robusta . Si desea que resuelva los argumentos de las rutas reales à larealpath -f
(disponibles en elcoreutils
paquete Linux ), reemplace las:a
líneas 3 y 4 con:A
.Para usar esto en zsh, elimine la primera y la última línea y colóquelo en un directorio que esté en su
$FPATH
variable.Script de prueba
Finalmente, el guión de prueba. Acepta una opción, a saber,
-v
habilitar la salida detallada.fuente
/
, me temo.La secuencia de comandos anterior se inspiró en pini's (¡Gracias!). Activa un error en el módulo de resaltado de sintaxis de Stack Overflow (al menos en mi marco de vista previa). Por lo tanto, ignore si el resaltado es incorrecto.
Algunas notas:
Excepto por las secuencias de barra invertida mencionadas, la última línea de función "relPath" genera nombres de ruta compatibles con python:
La última línea se puede reemplazar (y simplificar) por línea
Prefiero el último porque
Los nombres de archivo se pueden agregar directamente a las rutas de directorio obtenidas por relPath, por ejemplo:
Los enlaces simbólicos en el mismo directorio creado con este método no tienen lo feo
"./"
antepuesto al nombre del archivo.Listado de código para pruebas de regresión (simplemente añádalo al script de shell):
fuente
No muchas de las respuestas aquí son prácticas para el uso diario. Como es muy difícil hacer esto correctamente en puro bash, sugiero la siguiente solución confiable (similar a una sugerencia oculta en un comentario):
Luego, puede obtener la ruta relativa basada en el directorio actual:
o puede especificar que la ruta sea relativa a un directorio dado:
La única desventaja es que requiere python, pero:
Tenga en cuenta que las soluciones que incluyen
basename
odirname
pueden no ser necesariamente mejores, ya que requieren quecoreutils
se instalen. Si alguien tiene unabash
solución pura que sea confiable y simple (en lugar de una intrincada curiosidad), me sorprendería.fuente
Este script proporciona resultados correctos solo para entradas que son rutas absolutas o rutas relativas sin
.
o..
:fuente
Simplemente usaría Perl para esta tarea no tan trivial:
fuente
perl -MFile::Spec -e "print File::Spec->abs2rel('$absolute','$current')"
para que se cite absoluto y actual.relative=$(perl -MFile::Spec -e 'print File::Spec->abs2rel(@ARGV)' "$absolute" "$current")
. ¡Esto asegura que los valores no pueden, por sí mismos, contener código perl!Una ligera mejora en las respuestas de kasku y Pini , que juega mejor con los espacios y permite pasar caminos relativos:
fuente
test.sh:
Pruebas:
fuente
readlink
en$(pwd)
.Otra solución más, pure
bash
+ GNUreadlink
para un uso fácil en el siguiente contexto:Esto funciona en casi todos los Linux actuales. Si
readlink -m
no funciona a su lado, intente en sureadlink -f
lugar. Consulte también https://gist.github.com/hilbix/1ec361d00a8178ae8ea0 para posibles actualizaciones:Notas:
*
o?
.ln -s
:relpath / /
da.
y no la cadena vacíarelpath a a
daa
, incluso sia
resulta ser un directorioreadlink
lo es necesaria para canonizar las rutas.readlink -m
esto también funciona para rutas aún no existentes.En sistemas antiguos, donde
readlink -m
no está disponible,readlink -f
falla si el archivo no existe. Así que probablemente necesites alguna solución como esta (¡no probado!):Esto no es realmente correcto en caso de que
$1
incluya.
o..
para rutas no existentes (como en/doesnotexist/./a
), pero debería cubrir la mayoría de los casos.(Reemplace
readlink -m --
arriba porreadlink_missing
.)Editar debido al downvote sigue
Aquí hay una prueba de que esta función es correcta:
¿Perplejo? Bueno, estos son los resultados correctos ! Incluso si cree que no se ajusta a la pregunta, aquí está la prueba de que esto es correcto:
Sin ninguna duda,
../bar
es la ruta relativa exacta y única correcta de la páginabar
vista desde la páginamoo
. Todo lo demás estaría completamente equivocado.Es trivial adoptar el resultado a la pregunta que aparentemente supone, que
current
es un directorio:Esto devuelve exactamente lo que se solicitó.
Y antes de levantar una ceja, aquí hay una variante un poco más compleja de
relpath
(detectar la pequeña diferencia), que también debería funcionar para la sintaxis de URL (por lo que un seguimiento/
sobrevive, gracias a algo debash
magia):Y aquí están las comprobaciones solo para dejar en claro: Realmente funciona como se dijo.
Y así es como se puede usar para dar el resultado deseado de la pregunta:
Si encuentra algo que no funciona, hágamelo saber en los comentarios a continuación. Gracias.
PD:
¿Por qué son los argumentos de
relpath
"invertido" en contraste con todas las otras respuestas aquí?Si cambias
a
entonces puede dejar el segundo parámetro ausente, de modo que BASE sea el directorio / URL actual / lo que sea. Ese es solo el principio de Unix, como de costumbre.
Si no le gusta, vuelva a Windows. Gracias.
fuente
Lamentablemente, la respuesta de Mark Rushakoff (ahora eliminada; hace referencia al código desde aquí ) no parece funcionar correctamente cuando se adapta a:
El pensamiento esbozado en el comentario se puede refinar para que funcione correctamente en la mayoría de los casos. Estoy a punto de suponer que el script toma un argumento fuente (donde se encuentra) y un argumento objetivo (donde desea llegar), y que ambos son nombres de ruta absolutos o ambos son relativos. Si uno es absoluto y el otro relativo, lo más fácil es prefijar el nombre relativo con el directorio de trabajo actual, pero el siguiente código no lo hace.
Tener cuidado
El siguiente código está cerca de funcionar correctamente, pero no es del todo correcto.
xyz/./pqr
'.xyz/../pqr
'../
' de las rutas.El código de Dennis es mejor porque repara 1 y 5, pero tiene los mismos problemas 2, 3, 4. Utilice el código de Dennis (y vote por encima de esto) debido a eso.
(NB: POSIX proporciona una llamada al sistema
realpath()
que resuelve los nombres de ruta para que no queden enlaces simbólicos en ellos. Aplicar eso a los nombres de entrada y luego usar el código de Dennis daría la respuesta correcta cada vez. Es trivial escribir el código C que Wrapsrealpath()
, lo he hecho, pero no conozco una utilidad estándar que lo haga).Para esto, encuentro que Perl es más fácil de usar que shell, aunque bash tiene un soporte decente para las matrices y probablemente también podría hacer esto: ejercicio para el lector. Entonces, dados dos nombres compatibles, divídalos en componentes:
Así:
Script de prueba (los corchetes contienen un espacio en blanco y una pestaña):
Salida del script de prueba:
Este script de Perl funciona bastante bien en Unix (no tiene en cuenta todas las complejidades de los nombres de ruta de Windows) frente a entradas extrañas. Utiliza el módulo
Cwd
y su funciónrealpath
para resolver la ruta real de los nombres que existen, y realiza un análisis textual para las rutas que no existen. En todos los casos, excepto uno, produce el mismo resultado que el script de Dennis. El caso desviado es:Los dos resultados son equivalentes, pero no idénticos. (El resultado es de una versión ligeramente modificada del script de prueba: el script de Perl a continuación simplemente imprime la respuesta, en lugar de las entradas y la respuesta como en el script anterior.) Ahora: ¿debería eliminar la respuesta que no funciona? Tal vez...
fuente
/home/part1/part2
to/
tiene uno demasiado../
. De lo contrario, mi secuencia de comandos coincide con su salida, excepto que la mía agrega una innecesaria.
al final de donde está el destino.
y no uso una./
al comienzo de las que descienden sin subir.readlink /usr/bin/vi
da/etc/alternatives/vi
, pero ese es otro enlace simbólico - mientras quereadlink -f /usr/bin/vi
da/usr/bin/vim.basic
, que es el destino final de todos los enlaces simbólicos ...Tomé su pregunta como un desafío para escribir esto en código de shell "portátil", es decir
Se ejecuta en cualquier shell POSIX conforme (zsh, bash, ksh, ash, busybox, ...). Incluso contiene un traje de prueba para verificar su funcionamiento. La canonización de los nombres de ruta se deja como un ejercicio. :-)
fuente
Mi solución:
fuente
Aquí está mi versión. Se basa en la respuesta de @Offirmo . Lo hice compatible con Dash y solucioné el siguiente error de caso de prueba:
./compute-relative.sh "/a/b/c/de/f/g" "/a/b/c/def/g/"
->"../..f/g/"
Ahora:
CT_FindRelativePath "/a/b/c/de/f/g" "/a/b/c/def/g/"
->"../../../def/g/"
Ver el codigo:
fuente
Supongo que este también hará el truco ... (viene con pruebas integradas) :)
OK, se esperan algunos gastos generales, ¡pero estamos haciendo Bourne shell aquí! ;)
fuente
Este script solo funciona en los nombres de ruta. No requiere que exista ninguno de los archivos. Si las rutas pasadas no son absolutas, el comportamiento es un poco inusual, pero debería funcionar como se espera si ambas rutas son relativas.
Solo lo probé en OS X, por lo que podría no ser portátil.
fuente
Esta respuesta no aborda la parte Bash de la pregunta, pero debido a que intenté usar las respuestas en esta pregunta para implementar esta funcionalidad en Emacs , la arrojaré allí.
Emacs realmente tiene una función para esto fuera de la caja:
fuente
relpath
función) se comporta de manera idénticafile-relative-name
para los casos de prueba que ha proporcionado.Aquí hay un script de shell que lo hace sin llamar a otros programas:
fuente: http://www.ynform.org/w/Pub/Relpath
fuente
Necesitaba algo como esto, pero también resolvió los enlaces simbólicos. Descubrí que pwd tiene una bandera -P para ese propósito. Se adjunta un fragmento de mi script. Está dentro de una función en un script de shell, de ahí los $ 1 y $ 2. El valor del resultado, que es la ruta relativa de START_ABS a END_ABS, está en la variable UPDIRS. Los cd del script en cada directorio de parámetros para ejecutar el pwd -P y esto también significa que se manejan los parámetros de ruta relativa. Saludos, Jim
fuente