使用 Bash 将绝对路径转换为给定当前目录的相对路径
示例:
absolute="/foo/bar"
current="/foo/baz/foo"
# Magic
relative="../../bar"
如何创造魔法(希望代码不要太复杂......)?
Example:
absolute="/foo/bar"
current="/foo/baz/foo"
# Magic
relative="../../bar"
How do I create the magic (hopefully not too complicated code...)?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(25)
我认为使用 GNU coreutils 8.23 中的
realpath
是最简单的:例如:
-s
标志确保符号链接不会扩展。Using
realpath
from GNU coreutils 8.23 is the simplest, I think:For example:
The
-s
flag ensures that symlinks are not expanded.给出:
gives:
自 2001 年以来,它就内置于 Perl 中,因此它几乎适用于您能想象到的所有系统,甚至VMS。
此外,该解决方案很容易理解。
因此,对于您的示例:
...效果很好。
It is built in to Perl since 2001, so it works on nearly every system you can imagine, even VMS.
Also, the solution is easy to understand.
So for your example:
...would work fine.
这是对 @pini 目前评价最高的解决方案的更正、功能齐全的改进(遗憾的是它只处理少数情况)
提醒:
-z
测试字符串是否为零长度(=空)并且-n
测试字符串是否为空。测试用例:
This is a corrected, fully functional improvement of the currently best rated solution from @pini (which sadly handle only a few cases)
Reminder :
-z
test if the string is zero-length (=empty) and-n
test if the string is not empty.Test cases :
假设你已经安装了:bash、pwd、dirname、echo;那么 relpath 是
我已经从 pini 中得到了答案以及其他一些想法
< em>注意:这要求两个路径都是现有文件夹。文件将无法工作。
Presuming that you have installed: bash, pwd, dirname, echo; then relpath is
I've golfed the answer from pini and a few other ideas
Note: This requires both paths to be existing folders. Files will not work.
Python 的 os.path.relpath 作为 shell 函数
此 relpath 练习的目标是模仿 Python 2.7 的 os.path.relpath 函数(从 Python 2.6 版开始可用,但只能在 2.7 版中正常工作),由 xni 提议。因此,某些结果可能与其他答案中提供的功能不同。
(我没有在路径中使用换行符进行测试,只是因为它破坏了基于从 ZSH 调用 python -c 的验证。这肯定是可能的,需要一些努力。)
关于 Bash 中的“魔法”,我有很久以前我就放弃了在 Bash 中寻找魔法,但后来我在 ZSH 中找到了我需要的所有魔法,甚至还有一些。
因此,我提出了两种实现方式。
第一个实现的目标是完全符合 POSIX 标准。我已经在 Debian 6.0.6“Squeeze”上使用
/bin/dash
进行了测试。它还可以与 OS X 10.8.3 上的/bin/sh
完美配合,OS X 10.8.3 实际上是伪装成 POSIX shell 的 Bash 版本 3.2。第二种实现是 ZSH shell 函数,它对于路径中的多个斜杠和其他麻烦具有鲁棒性。如果您有可用的 ZSH,则这是推荐的版本,即使您从另一个 shell 以下面提供的脚本形式调用它(即使用
#!/usr/bin/env zsh
的 shebang) 。最后,我编写了一个 ZSH 脚本,根据其他答案中提供的测试用例,验证在
$PATH
中找到的relpath
命令的输出。我通过添加一些空格、制表符和标点符号(例如!)为这些测试添加了一些趣味。 ? *
在这里和那里,还用 vim-powerline 中发现的奇异 UTF-8 字符进行了另一个测试。POSIX shell 函数
首先,符合 POSIX 的 shell 函数。它适用于各种路径,但不会清除多个斜杠或解析符号链接。
ZSH shell 功能
现在,更强大的
zsh
版本。如果您希望它按照realpath -f
(在 Linuxcoreutils
包中提供)的方式解析真实路径的参数,请替换:a
在第 3 行和第 4 行使用:A
。要在 zsh 中使用它,请删除第一行和最后一行并将其放入
$FPATH
变量中的目录中。测试脚本
最后是测试脚本。它接受一个选项,即
-v
来启用详细输出。Python's
os.path.relpath
as a shell functionThe goal of this
relpath
exercise is to mimic Python 2.7'sos.path.relpath
function (available from Python version 2.6 but only working properly in 2.7), as proposed by xni. As a consequence, some of the results may differ from functions provided in other answers.(I have not tested with newlines in paths simply because it breaks the validation based on calling
python -c
from ZSH. It would certainly be possible with some effort.)Regarding “magic” in Bash, I have given up looking for magic in Bash long ago, but I have since found all the magic I need, and then some, in ZSH.
Consequently, I propose two implementations.
The first implementation aims to be fully POSIX-compliant. I have tested it with
/bin/dash
on Debian 6.0.6 “Squeeze”. It also works perfectly with/bin/sh
on OS X 10.8.3, which is actually Bash version 3.2 pretending to be a POSIX shell.The second implementation is a ZSH shell function that is robust against multiple slashes and other nuisances in paths. If you have ZSH available, this is the recommended version, even if you are calling it in the script form presented below (i.e. with a shebang of
#!/usr/bin/env zsh
) from another shell.Finally, I have written a ZSH script that verifies the output of the
relpath
command found in$PATH
given the test cases provided in other answers. I added some spice to those tests by adding some spaces, tabs, and punctuation such as! ? *
here and there and also threw in yet another test with exotic UTF-8 characters found in vim-powerline.POSIX shell function
First, the POSIX-compliant shell function. It works with a variety of paths, but does not clean multiple slashes or resolve symlinks.
ZSH shell function
Now, the more robust
zsh
version. If you would like it to resolve the arguments to real paths à larealpath -f
(available in the Linuxcoreutils
package), replace the:a
on lines 3 and 4 with:A
.To use this in zsh, remove the first and last line and put it in a directory that is in your
$FPATH
variable.Test script
Finally, the test script. It accepts one option, namely
-v
to enable verbose output.上面的 shell 脚本的灵感来自于 pini 的(谢谢!)。它会触发一个错误
在 Stack Overflow 的语法高亮模块中(至少在我的预览中)
框架)。因此,如果突出显示不正确,请忽略。
一些注释:
长度和复杂性
shell(在 Ubuntu Linux 12.04 中使用 dash、bash 和 zsh 进行测试)
污染全局名称空间
反斜杠、制表符、'、"、?、*、[、] 等。
规范的绝对目录路径作为参数
相对的,非规范的)但需要外部 GNU 核心实用程序“readlink”
在不同的 shell 中表现不同 -> POSIX 建议 printf
优于 echo。
序列,从而破坏包含此类序列的路径名
以及 shell 和操作系统实用程序所期望的(例如 cd、ln、ls、find、mkdir;
与 python 的“os.path.relpath”不同,它会解释一些反斜杠
序列)
除了提到的反斜杠序列之外,函数“relPath”的最后一行
输出与 python 兼容的路径名:
最后一行可以由行替换(并简化)
我更喜欢后者,因为
文件名可以直接附加到relPath获取的dir路径中,例如:
使用此方法创建的同一目录中的符号链接没有
文件名前面加上丑陋的
"./"
。修复它。
回归测试的代码列表(只需将其附加到 shell 脚本中):
Above shell script was inspired by pini's (Thanks!). It triggers a bug
in the syntax highlighting module of Stack Overflow (at least in my preview
frame). So please ignore if highlighting is incorrect.
Some notes:
length and complexity
shells (tested with dash, bash, and zsh in Ubuntu Linux 12.04)
polluting the global name space
backslashes, tabs, ', ", ?, *, [, ], etc.
canonical absolute directory paths as parameters
relative, non-canonical) but requires external GNU core utility "readlink"
behaves differently in different shells -> POSIX recommends that printf
is preferred over echo.
sequences and thus corrupt pathnames containing such sequences
and expected by shell and OS utilities (e.g. cd, ln, ls, find, mkdir;
unlike python's "os.path.relpath" which will interpret some backslash
sequences)
Except for the mentioned backslash sequences the last line of function "relPath"
outputs pathnames compatible to python:
Last line can be replaced (and simplified) by line
I prefer the latter because
Filenames can be directly appended to dir paths obtained by relPath, e.g.:
Symbolic links in the same dir created with this method do not have
the ugly
"./"
prepended to the filename.to fix it.
Code listing for regression tests (simply append it to the shell script):
这里的很多答案并不适合日常使用。由于在纯 bash 中很难正确执行此操作,因此我建议使用以下可靠的解决方案(类似于注释中隐藏的一个建议):
然后,您可以根据当前目录获取相对路径:
或者您可以指定路径相对于给定目录:
一个缺点是这需要 python,但是:
例如,如果文件名包含以下内容,许多其他解决方案将不起作用
空格或其他特殊字符。
请注意,包含
basename
或dirname
的解决方案不一定更好,因为它们需要安装coreutils
。如果有人有一个可靠且简单的纯 bash 解决方案(而不是令人费解的好奇心),我会感到惊讶。Not a lot of the answers here are practical for every day use. Since it is very difficult to do this properly in pure bash, I suggest the following, reliable solution (similar to one suggestion buried in a comment):
Then, you can get the relative path based upon the current directory:
or you can specify that the path be relative to a given directory:
The one disadvantage is this requires python, but:
For example, many other solutions do not work if filenames contain
spaces or other special characters.
Note that solutions which include
basename
ordirname
may not necessarily be better, as they require thatcoreutils
be installed. If somebody has a purebash
solution that is reliable and simple (rather than a convoluted curiosity), I'd be surprised.此脚本仅对不带
.
或..
的绝对路径或相对路径输入提供正确的结果:This script gives correct results only for inputs that are absolute paths or relative paths without
.
or..
:我只想使用 Perl 来完成这个不那么简单的任务:
I would just use Perl for this not-so-trivial task:
另一种解决方案是纯
bash
+ GNUreadlink
,以便在以下上下文中轻松使用:这适用于几乎所有当前的 Linux。如果
readlink -m
在您这边不起作用,请尝试readlink -f
。另请参阅 https://gist.github.com/hilbix/1ec361d00a8178ae8ea0 了解可能的更新: :
*
或?
,则可以安全地防止不需要的 shell 元字符扩展。relpath //
给出.
而不是空字符串relpath a a
给出a
,即使a
恰好是一个目录readlink
来规范化路径。readlink -m
它也适用于尚不存在的路径。在旧系统上,
readlink -m
不可用,如果文件不存在,readlink -f
将失败。因此,您可能需要一些像这样的解决方法(未经测试!):如果
$1
包含.
或..
对于不存在的情况,这实际上并不完全正确路径(如/doesnotexist/./a
中),但它应该涵盖大多数情况。(将上面的
readlink -m --
替换为readlink_missing
。)由于反对票而编辑
如下 这是一个测试,该函数确实是正确的:
困惑吗?好吧,这些是正确的结果!即使您认为它不符合问题,这里也可以证明这是正确的:
毫无疑问,
../bar
是页面bar< 的准确且唯一正确的相对路径/code> 从页面
moo
看到。其他一切都是完全错误的。采用显然假设
current
是一个目录的问题的输出是微不足道的:这准确地返回了所要求的内容。
在你扬起眉毛之前,这里有一个更复杂的
relpath
变体(发现细微的差别),它也应该适用于 URL 语法(因此尾随/
幸存下来,这要归功于一些bash
-magic):这里是一些检查,只是为了澄清:它确实按照所说的那样工作。
以下是如何使用它来给出问题所需的结果:
如果您发现某些内容不起作用,请在下面的评论中告诉我。谢谢。
PS:
为什么 relpath 的参数与这里的所有其他答案相比“相反”?
如果更改
为,
则可以保留第二个参数,这样 BASE 就是当前目录/URL/其他内容。像往常一样,这只是 Unix 原理。
Yet another solution, pure
bash
+ GNUreadlink
for easy use in following context:This works in nearly all current Linux. If
readlink -m
does not work at your side, tryreadlink -f
instead. See also https://gist.github.com/hilbix/1ec361d00a8178ae8ea0 for possible updates:Notes:
*
or?
.ln -s
:relpath / /
gives.
and not the empty stringrelpath a a
givesa
, even ifa
happens to be a directoryreadlink
is required to canonicalize paths.readlink -m
it works for not yet existing paths, too.On old systems, where
readlink -m
is not available,readlink -f
fails if the file does not exist. So you probably need some workaround like this (untested!):This is not really quite correct in case
$1
includes.
or..
for nonexisting paths (like in/doesnotexist/./a
), but it should cover most cases.(Replace
readlink -m --
above byreadlink_missing
.)Edit because of the downvote follows
Here is a test, that this function, indeed, is correct:
Puzzled? Well, these are the correct results! Even if you think it does not fit the question, here is the proof this is correct:
Without any doubt,
../bar
is the exact and only correct relative path of the pagebar
seen from the pagemoo
. Everything else would be plain wrong.It is trivial to adopt the output to the question which apparently assumes, that
current
is a directory:This returns exactly, what was asked for.
And before you raise an eyebrow, here is a bit more complex variant of
relpath
(spot the small difference), which should work for URL-Syntax, too (so a trailing/
survives, thanks to somebash
-magic):And here are the checks just to make clear: It really works as told.
And here is how this can be used to give the wanted result from the question:
If you find something which does not work, please let me know in the comments below. Thanks.
PS:
Why are the arguments of
relpath
"reversed" in contrast to all the other answers here?If you change
to
then you can leave the 2nd parameter away, such that the BASE is the current directory/URL/whatever. That's only the Unix principle, as usual.
对 kasku 和 略有改进Pini 的 答案,它可以更好地处理空格并允许传递相对路径:
A slight improvement on kasku's and Pini's answers, which plays nicer with spaces and allows passing relative paths:
可悲的是,Mark Rushakoff 的答案(现已删除 - 它引用了 此处)在适应时似乎无法正常工作:
可以对评论中概述的思维进行细化,使其在大多数情况下都能正确工作。我将假设该脚本采用源参数(您所在的位置)和目标参数(您想要到达的位置),并且两者都是绝对路径名或都是相对路径名。如果一个是绝对名称,另一个是相对名称,最简单的方法就是在相对名称前加上当前工作目录的前缀 - 但下面的代码不会这样做。
请注意,
下面的代码接近正常工作,但并不完全正确。
xyz/./pqr
”等路径中的杂散“点”。xyz/../pqr
”等路径中的杂散“双点”。./
”。Dennis 的代码更好,因为它修复了 1 和 5 - 但具有相同的问题 2、3、4。
因此,请使用丹尼斯的代码(并在此之前对其进行投票)。
(注意:POSIX 提供了一个系统调用
realpath()
来解析路径名,以便其中不存在符号链接。将其应用于输入名称,然后使用 Dennis 的代码每次都会给出正确的答案编写包装realpath()
的 C 代码很简单 - 我已经完成了 - 但我不知道有这样做的标准实用程序。)对此,我发现 Perl 更容易。与 shell 相比,尽管 bash 对数组有很好的支持,并且可能也可以做到这一点 - 供读者练习。因此,给定两个兼容的名称,将它们分别拆分为组件:
因此:
测试脚本(方括号包含一个空格和一个制表符):
测试脚本的输出:
面对奇怪的情况,这个 Perl 脚本在 Unix 上工作得相当彻底(它没有考虑 Windows 路径名的所有复杂性)输入。它使用模块
Cwd
及其函数realpath
来解析存在的名称的真实路径,并对不存在的路径进行文本分析。在除一种情况外的所有情况下,它都会生成与 Dennis 脚本相同的输出。异常情况是:两个结果是等效的 - 只是不相同。 (输出来自测试脚本的轻微修改版本 - 下面的 Perl 脚本只是打印答案,而不是像上面的脚本中那样打印输入和答案。)现在:我应该消除不起作用的答案吗?也许...
Sadly, Mark Rushakoff's answer (now deleted - it referenced the code from here) does not seem to work correctly when adapted to:
The thinking outlined in the commentary can be refined to make it work correctly for most cases. I'm about to assume that the script takes a source argument (where you are) and a target argument (where you want to get to), and that either both are absolute pathnames or both are relative. If one is absolute and the other relative, the easiest thing is to prefix the relative name with the current working directory - but the code below does not do that.
Beware
The code below is close to working correctly, but is not quite right.
xyz/./pqr
'.xyz/../pqr
'../
' from paths.Dennis's code is better because it fixes 1 and 5 - but has the same issues 2, 3, 4.
Use Dennis's code (and up-vote it ahead of this) because of that.
(NB: POSIX provides a system call
realpath()
that resolves pathnames so that there are no symlinks left in them. Applying that to the input names, and then using Dennis's code would give the correct answer each time. It is trivial to write the C code that wrapsrealpath()
- I've done it - but I don't know of a standard utility that does so.)For this, I find Perl easier to use than shell, though bash has decent support for arrays and could probably do this too - exercise for the reader. So, given two compatible names, split them each into components:
Thus:
Test script (the square brackets contain a blank and a tab):
Output from the test script:
This Perl script works fairly thoroughly on Unix (it does not take into account all the complexities of Windows path names) in the face of weird inputs. It uses the module
Cwd
and its functionrealpath
to resolve the real path of names that exist, and does a textual analysis for paths that don't exist. In all cases except one, it produces the same output as Dennis's script. The deviant case is:The two results are equivalent - just not identical. (The output is from a mildly modified version of the test script - the Perl script below simply prints the answer, rather than the inputs and the answer as in the script above.) Now: should I eliminate the non-working answer? Maybe...
test.sh:
测试:
test.sh:
Testing:
我将你的问题视为在“可移植”shell 代码中编写此代码的挑战,即
它可以在任何符合 POSIX 的 shell(zsh、bash、ksh、ash、busybox 等)上运行。它甚至包含一个测试套件来验证其操作。路径名的规范化留作练习。 :-)
I took your question as a challenge to write this in "portable" shell code, i.e.
It runs on any POSIX conformant shell (zsh, bash, ksh, ash, busybox, ...). It even contains a testsuite to verify its operation. Canonicalization of pathnames is left as an exercise. :-)
我的解决方案:
My Solution:
该脚本仅适用于路径名。它不需要任何文件存在。如果传递的路径不是绝对的,则行为有点不寻常,但如果两个路径都是相对的,它应该按预期工作。
我只在 OS X 上测试过它,所以它可能不可移植。
This script works only on the path names. It does not require any of the files to exist. If the paths passed are not absolute, the behavior is a bit unusual, but it should work as expected if both paths are relative.
I only tested it on OS X, so it might not be portable.
这是我的版本。它基于 的答案 @Offirmo。我使其与 Dash 兼容并修复了以下测试用例失败:
./compute-relative.sh "/a/b/c/de/f/g" "/a/b/c/def/g/"
-->“../..f/g/”
现在:
CT_FindRelativePath“/a/b/c/de/f/g”“/a/b/c/def/g/ “
-->"../../../def/g/"
查看代码:
Here is my version. It's based on the answer by @Offirmo. I made it Dash-compatible and fixed the following testcase failure:
./compute-relative.sh "/a/b/c/de/f/g" "/a/b/c/def/g/"
-->"../..f/g/"
Now:
CT_FindRelativePath "/a/b/c/de/f/g" "/a/b/c/def/g/"
-->"../../../def/g/"
See the code:
我使用的 macOS 默认情况下没有
realpath
命令,因此我创建了一个pure bash
函数来计算它。I am using macOS that did not have
realpath
command by default, so I made apure bash
function to caculate it.我猜这个也能解决问题...(带有内置测试):)
好的,预计会有一些开销,但我们在这里使用 Bourne shell! ;)
Guess this one shall do the trick too... (comes with built-in tests) :)
OK, some overhead expected, but we're doing Bourne shell here! ;)
这个答案没有解决问题的 Bash 部分,而是因为我尝试使用这个问题中的答案来实现此功能 Emacs 我会把它扔到那里。
Emacs 实际上有一个开箱即用的函数:
This answer does not address the Bash part of the question, but because I tried to use the answers in this question to implement this functionality in Emacs I'll throw it out there.
Emacs actually has a function for this out of the box:
在bash中:
in bash:
下面是一个 shell 脚本,它无需调用其他程序即可完成此操作:
来源:http://www.ynform。 org/w/Pub/Relpath
Here's a shell script that does it without calling other programs:
source: http://www.ynform.org/w/Pub/Relpath
我需要这样的东西,但它也解决了符号链接。我发现 pwd 有一个用于此目的的 -P 标志。附上我的脚本片段。它位于 shell 脚本的函数内,因此是 $1 和 $2。结果值是从 START_ABS 到 END_ABS 的相对路径,位于 UPDIRS 变量中。脚本 cd 进入每个参数目录以执行 pwd -P,这也意味着处理相对路径参数。干杯,吉姆
I needed something like this but which resolved symbolic links too. I discovered that pwd has a -P flag for that purpose. A fragment of my script is appended. It's within a function in a shell script, hence the $1 and $2. The result value, which is the relative path from START_ABS to END_ABS, is in the UPDIRS variable. The script cd's into each parameter directory in order to execute the pwd -P and this also means that relative path parameters are handled. Cheers, Jim