使用 Bash 将给定当前目录的绝对路径转换为相对路径
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2564634/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Convert absolute path into relative path given a current directory using Bash
提问by Paul Tarjan
Example:
例子:
absolute="/foo/bar"
current="/foo/baz/foo"
# Magic
relative="../../bar"
How do I create the magic (hopefully not too complicated code...)?
我如何创造魔法(希望不是太复杂的代码......)?
回答by modulus0
Using realpath from GNU coreutils 8.23 is the simplest, I think:
我认为使用 GNU coreutils 8.23 中的 realpath 是最简单的:
$ realpath --relative-to="$file1" "$file2"
For example:
例如:
$ realpath --relative-to=/usr/bin/nmap /tmp/testing
../../../tmp/testing
回答by xni
$ python -c "import os.path; print os.path.relpath('/foo/bar', '/foo/baz/foo')"
gives:
给出:
../../bar
回答by Offirmo
This is a corrected, fully functional improvement of the currently best rated solution from @pini (which sadly handle only a few cases)
这是对@pini 目前评价最高的解决方案的更正、功能齐全的改进(遗憾的是它只处理少数情况)
Reminder : '-z' test if the string is zero-length (=empty) and '-n' test if the string is notempty.
提醒:“-z”测试如果字符串是零长度(=空)和“-n”测试如果字符串是不空。
# both and are absolute paths beginning with /
# returns relative path to /$target from /$source
source=
target=
common_part=$source # for now
result="" # for now
while [[ "${target#$common_part}" == "${target}" ]]; do
# no match, means that candidate common part is not correct
# go up one level (reduce common part)
common_part="$(dirname $common_part)"
# and record that we went back, with correct / handling
if [[ -z $result ]]; then
result=".."
else
result="../$result"
fi
done
if [[ $common_part == "/" ]]; then
# special case for root (no common path)
result="$result/"
fi
# since we now have identified the common part,
# compute the non-common part
forward_part="${target#$common_part}"
# and now stick all parts together
if [[ -n $result ]] && [[ -n $forward_part ]]; then
result="$result$forward_part"
elif [[ -n $forward_part ]]; then
# extra slash removal
result="${forward_part:1}"
fi
echo $result
Test cases :
测试用例 :
compute_relative.sh "/A/B/C" "/A" --> "../.."
compute_relative.sh "/A/B/C" "/A/B" --> ".."
compute_relative.sh "/A/B/C" "/A/B/C" --> ""
compute_relative.sh "/A/B/C" "/A/B/C/D" --> "D"
compute_relative.sh "/A/B/C" "/A/B/C/D/E" --> "D/E"
compute_relative.sh "/A/B/C" "/A/B/D" --> "../D"
compute_relative.sh "/A/B/C" "/A/B/D/E" --> "../D/E"
compute_relative.sh "/A/B/C" "/A/D" --> "../../D"
compute_relative.sh "/A/B/C" "/A/D/E" --> "../../D/E"
compute_relative.sh "/A/B/C" "/D/E/F" --> "../../../D/E/F"
回答by pini
#!/bin/bash
# both and are absolute paths
# returns relative to
source=
target=
common_part=$source
back=
while [ "${target#$common_part}" = "${target}" ]; do
common_part=$(dirname $common_part)
back="../${back}"
done
echo ${back}${target#$common_part/}
回答by Erik Aronesty
It is built in to Perlsince 2001, so it works on nearly every system you can imagine, even VMS.
它自 2001 年就内置于Perl 中,因此它几乎适用于您可以想象的所有系统,甚至VMS。
perl -e 'use File::Spec; print File::Spec->abs2rel(@ARGV) . "\n"' FILE BASE
Also, the solution is easy to understand.
此外,该解决方案很容易理解。
So for your example:
所以对于你的例子:
perl -e 'use File::Spec; print File::Spec->abs2rel(@ARGV) . "\n"' $absolute $current
...would work fine.
...会工作得很好。
回答by Alexx Roche
Presuming that you have installed: bash, pwd, dirname, echo; then relpath is
假设你已经安装了:bash、pwd、dirname、echo;那么 relpath 是
#!/bin/bash
s=$(cd ${1%%/};pwd); d=$(cd ;pwd); b=; while [ "${d#$s/}" == "${d}" ]
do s=$(dirname $s);b="../${b}"; done; echo ${b}${d#$s/}
I've golfed the answer from piniand a few other ideas
我已经从pini和其他一些想法中得到了答案
Note: This requires both paths to be existing folders. Files will notwork.
注意:这要求两个路径都是现有文件夹。文件将无法正常工作。
回答by simonair
Python's os.path.relpath
as a shell function
Pythonos.path.relpath
作为 shell 函数
The goal of this relpath
exercise is to mimic Python 2.7's os.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.
本relpath
练习的目标是模仿xnios.path.relpath
提出的Python 2.7 的功能(可从 Python 2.6 版获得,但只能在 2.7 中正常工作)。因此,某些结果可能与其他答案中提供的函数不同。
(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.)
(我没有在路径中使用换行符进行测试,仅仅是因为它破坏了基于python -c
ZSH调用的验证。通过一些努力肯定是可能的。)
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.
关于 Bash 中的“魔法”,我很久以前就已经放弃在 Bash 中寻找魔法了,但此后我在 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.
第一个实现旨在完全符合 POSIX。我已经/bin/dash
在 Debian 6.0.6 “Squeeze”上对其进行了测试 。它也适用/bin/sh
于 OS X 10.8.3,它实际上是伪装成 POSIX shell 的 Bash 3.2 版。
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.
第二个实现是一个 ZSH shell 函数,它对路径中的多个斜杠和其他麻烦有很强的抵抗力。如果您有可用的 ZSH,这是推荐的版本,即使您是#!/usr/bin/env zsh
从另一个 shell以下面显示的脚本形式(即使用 的 shebang )调用它也是如此。
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.
最后,我编写了一个 ZSH 脚本,用于验证在其他答案relpath
中$PATH
提供的测试用例中找到的命令的输出。我通过! ? *
在这里和那里添加一些空格、制表符和标点符号为这些测试添加了一些趣味,并且还使用vim-powerline 中的奇异 UTF-8 字符进行了另一个测试。
POSIXshell function
POSIX外壳函数
First, the POSIX-compliant shell function. It works with a variety of paths, but does not clean multiple slashes or resolve symlinks.
首先,符合 POSIX 的 shell 函数。它适用于多种路径,但不会清除多个斜杠或解析符号链接。
#!/bin/sh
relpath () {
[ $# -ge 1 ] && [ $# -le 2 ] || return 1
current="${2:+""}"
target="${2:-""}"
[ "$target" != . ] || target=/
target="/${target##/}"
[ "$current" != . ] || current=/
current="${current:="/"}"
current="/${current##/}"
appendix="${target##/}"
relative=''
while appendix="${target#"$current"/}"
[ "$current" != '/' ] && [ "$appendix" = "$target" ]; do
if [ "$current" = "$appendix" ]; then
relative="${relative:-.}"
echo "${relative#/}"
return 0
fi
current="${current%/*}"
relative="$relative${relative:+/}.."
done
relative="$relative${relative:+${appendix:+/}}${appendix#/}"
echo "$relative"
}
relpath "$@"
ZSH shell function
ZSH 外壳函数
Now, the more robust zsh
version. If you would like it to resolve the arguments to real paths à la realpath -f
(available in the Linux coreutils
package), replace the :a
on lines 3 and 4 with :A
.
现在,更强大的zsh
版本。如果您希望它将参数解析为实际路径 à la realpath -f
(在 Linuxcoreutils
包中可用),请将第:a
3 行和第 4 行的:A
.
To use this in zsh, remove the first and last line and put it in a directory that is in your $FPATH
variable.
要在 zsh 中使用它,请删除第一行和最后一行并将其放在$FPATH
变量中的目录中。
#!/usr/bin/env zsh
relpath () {
[[ $# -ge 1 ]] && [[ $# -le 2 ]] || return 1
local target=${${2:-}:a} # replace `:a' by `:A` to resolve symlinks
local current=${${${2:+}:-$PWD}:a} # replace `:a' by `:A` to resolve symlinks
local appendix=${target#/}
local relative=''
while appendix=${target#$current/}
[[ $current != '/' ]] && [[ $appendix = $target ]]; do
if [[ $current = $appendix ]]; then
relative=${relative:-.}
print ${relative#/}
return 0
fi
current=${current%/*}
relative="$relative${relative:+/}.."
done
relative+=${relative:+${appendix:+/}}${appendix#/}
print $relative
}
relpath "$@"
Test script
测试脚本
Finally, the test script. It accepts one option, namely -v
to enable verbose output.
最后,测试脚本。它接受一个选项,即-v
启用详细输出。
#!/usr/bin/env zsh
set -eu
VERBOSE=false
script_name=$(basename #!/bin/sh
# Return relative path from canonical absolute dir path to canonical
# absolute dir path ( and/or may end with one or no "/").
# Does only need POSIX shell builtins (no external command)
relPath () {
local common path up
common=${1%/} path=${2%/}/
while test "${path#"$common"/}" = "$path"; do
common=${common%/*} up=../$up
done
path=$up${path#"$common"/}; path=${path%/}; printf %s "${path:-.}"
}
# Return relative path from dir to dir (Does not impose any
# restrictions on and but requires GNU Core Utility "readlink"
# HINT: busybox's "readlink" does not support option '-m', only '-f'
# which requires that all but the last path component must exist)
relpath () { relPath "$(readlink -m "")" "$(readlink -m "")"; }
)
usage () {
print "\n Usage: $script_name SRC_PATH DESTINATION_PATH\n" >&2
exit ${1:=1}
}
vrb () { $VERBOSE && print -P ${(%)@} || return 0; }
relpath_check () {
[[ $# -ge 1 ]] && [[ $# -le 2 ]] || return 1
target=${${2:-}}
prefix=${${${2:+}:-$PWD}}
result=$(relpath $prefix $target)
# Compare with python's os.path.relpath function
py_result=$(python -c "import os.path; print os.path.relpath('$target', '$prefix')")
col='%F{green}'
if [[ $result != $py_result ]] && col='%F{red}' || $VERBOSE; then
print -P "${col}Source: '$prefix'\nDestination: '$target'%f"
print -P "${col}relpath: ${(qq)result}%f"
print -P "${col}python: ${(qq)py_result}%f\n"
fi
}
run_checks () {
print "Running checks..."
relpath_check '/ a b/?/?*/!' '/ a b/?/?/x??/?'
relpath_check '/' '/A'
relpath_check '/A' '/'
relpath_check '/ & / !/*/\/E' '/'
relpath_check '/' '/ & / !/*/\/E'
relpath_check '/ & / !/*/\/E' '/ & / !/?/\/E/F'
relpath_check '/X/Y' '/ & / !/C/\/E/F'
relpath_check '/ & / !/C' '/A'
relpath_check '/A / !/C' '/A /B'
relpath_check '/?/ !/C' '/?/ !/C'
relpath_check '/ & /B / C' '/ & /B / C/D'
relpath_check '/ & / !/C' '/ & / !/C/\/ê'
relpath_check '/?/ !/C' '/?/ !/D'
relpath_check '/.A /*B/C' '/.A /*B/\/E'
relpath_check '/ & / !/C' '/ & /D'
relpath_check '/ & / !/C' '/ & /\/E'
relpath_check '/ & / !/C' '/\/E/F'
relpath_check /home/part1/part2 /home/part1/part3
relpath_check /home/part1/part2 /home/part4/part5
relpath_check /home/part1/part2 /work/part6/part7
relpath_check /home/part1 /work/part1/part2/part3/part4
relpath_check /home /work/part2/part3
relpath_check / /work/part2/part3/part4
relpath_check /home/part1/part2 /home/part1/part2/part3/part4
relpath_check /home/part1/part2 /home/part1/part2/part3
relpath_check /home/part1/part2 /home/part1/part2
relpath_check /home/part1/part2 /home/part1
relpath_check /home/part1/part2 /home
relpath_check /home/part1/part2 /
relpath_check /home/part1/part2 /work
relpath_check /home/part1/part2 /work/part1
relpath_check /home/part1/part2 /work/part1/part2
relpath_check /home/part1/part2 /work/part1/part2/part3
relpath_check /home/part1/part2 /work/part1/part2/part3/part4
relpath_check home/part1/part2 home/part1/part3
relpath_check home/part1/part2 home/part4/part5
relpath_check home/part1/part2 work/part6/part7
relpath_check home/part1 work/part1/part2/part3/part4
relpath_check home work/part2/part3
relpath_check . work/part2/part3
relpath_check home/part1/part2 home/part1/part2/part3/part4
relpath_check home/part1/part2 home/part1/part2/part3
relpath_check home/part1/part2 home/part1/part2
relpath_check home/part1/part2 home/part1
relpath_check home/part1/part2 home
relpath_check home/part1/part2 .
relpath_check home/part1/part2 work
relpath_check home/part1/part2 work/part1
relpath_check home/part1/part2 work/part1/part2
relpath_check home/part1/part2 work/part1/part2/part3
relpath_check home/part1/part2 work/part1/part2/part3/part4
print "Done with checks."
}
if [[ $# -gt 0 ]] && [[ = "-v" ]]; then
VERBOSE=true
shift
fi
if [[ $# -eq 0 ]]; then
run_checks
else
VERBOSE=true
relpath_check "$@"
fi
回答by linuxball
path=$up${path#"$common"/}; path=${path%/}; printf %s "${path:-.}"
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.
以上 shell 脚本的灵感来自pini 的(谢谢!)。它在 Stack Overflow 的语法高亮模块中触发了一个错误(至少在我的预览框架中)。因此,如果突出显示不正确,请忽略。
Some notes:
一些注意事项:
- Removed errors and improved code without significantly increasing code length and complexity
- Put functionality into functions for easiness of use
- Kept functions POSIX compatible so that they (should) work with all POSIX shells (tested with dash, bash, and zsh in Ubuntu Linux 12.04)
- Used local variables only to avoid clobbering global variables and polluting the global name space
- Both directory paths DO NOT need to exist (requirement for my application)
- Pathnames may contain spaces, special characters, control characters, backslashes, tabs, ', ", ?, *, [, ], etc.
- Core function "relPath" uses POSIX shell builtins only but requires canonical absolute directory paths as parameters
- Extended function "relpath" can handle arbitrary directory paths (also relative, non-canonical) but requires external GNU core utility "readlink"
- Avoided builtin "echo" and used builtin "printf" instead for two reasons:
- Due to conflicting historical implementations of builtin "echo" it behaves differently in different shells -> POSIX recommends that printf is preferred over echo.
- Builtin "echo" of some POSIX shells will interpret some backslash sequencesand thus corrupt pathnames containing such sequences
- To avoid unnecessary conversions, pathnames are used as they are returned 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:
printf %s "$up${path#"$common"/}"
Last line can be replaced (and simplified) by line
ln -s "$(relpath "<fromDir>" "<toDir>")<file>" "<fromDir>"
I prefer the latter because
Filenames can be directly appended to dir paths obtained by relPath, e.g.:
path=$up${path#"$common"/}; path=${path%/}; printf %s "${path:-.}"
Symbolic links in the same dir created with this method do not have the ugly
"./"
prepended to the filename.
- If you find an error please contact linuxball (at) gmail.com and I'll try to fix it.
- Added regression test suite (also POSIX shell compatible)
- 在不显着增加代码长度和复杂性的情况下消除错误并改进代码
- 将功能放入函数中以方便使用
- 保持函数 POSIX 兼容,以便它们(应该)与所有 POSIX shell 一起工作(在 Ubuntu Linux 12.04 中使用 dash、bash 和 zsh 进行测试)
- 仅使用局部变量以避免破坏全局变量和污染全局命名空间
- 两个目录路径都不需要存在(我的应用程序的要求)
- 路径名可能包含空格、特殊字符、控制字符、反斜杠、制表符、'、"、?、*、[、] 等。
- 核心函数“relPath”仅使用 POSIX shell 内置函数,但需要规范的绝对目录路径作为参数
- 扩展函数“relpath”可以处理任意目录路径(也是相对的、非规范的),但需要外部 GNU 核心实用程序“readlink”
- 避免使用内置的“echo”并使用内置的“printf”,原因有两个:
- 由于内置“echo”的历史实现存在冲突,它在不同的 shell 中表现不同 -> POSIX 建议 printf 优于 echo。
- 一些 POSIX shell 的内置“echo”将解释一些反斜杠序列,从而破坏包含这些序列的路径名
- 为避免不必要的转换,在 shell 和 OS 实用程序返回和预期时使用路径名(例如 cd、ln、ls、find、mkdir;与 python 的“os.path.relpath”不同,它会解释一些反斜杠序列)
除了提到的反斜杠序列,函数“relPath”的最后一行输出与python兼容的路径名:
printf %s "$up${path#"$common"/}"
最后一行可以由行替换(和简化)
ln -s "$(relpath "<fromDir>" "<toDir>")<file>" "<fromDir>"
我更喜欢后者,因为
文件名可以直接附加到 relPath 获得的目录路径,例如:
############################################################################ # If called with 2 arguments assume they are dir paths and print rel. path # ############################################################################ test "$#" = 2 && { printf '%s\n' "Rel. path from '' to '' is '$(relpath "" "")'." exit 0 } ####################################################### # If NOT called with 2 arguments run regression tests # ####################################################### format="\t%-19s %-22s %-27s %-8s %-8s %-8s\n" printf \ "\n\n*** Testing own and python's function with canonical absolute dirs\n\n" printf "$format\n" \ "From Directory" "To Directory" "Rel. Path" "relPath" "relpath" "python" IFS= while read -r p; do eval set -- $p case in '#'*|'') continue;; esac # Skip comments and empty lines # q stores quoting character, use " if ' is used in path name q="'"; case in *"'"*) q='"';; esac rPOk=passed rP=$(relPath "" ""); test "$rP" = "" || rPOk=$rP rpOk=passed rp=$(relpath "" ""); test "$rp" = "" || rpOk=$rp RPOk=passed RP=$(python -c "import os.path; print os.path.relpath($q$q, $q$q)") test "$RP" = "" || RPOk=$RP printf \ "$format" "$q$q" "$q$q" "$q$q" "$q$rPOk$q" "$q$rpOk$q" "$q$RPOk$q" done <<-"EOF" # From directory To directory Expected relative path '/' '/' '.' '/usr' '/' '..' '/usr/' '/' '..' '/' '/usr' 'usr' '/' '/usr/' 'usr' '/usr' '/usr' '.' '/usr/' '/usr' '.' '/usr' '/usr/' '.' '/usr/' '/usr/' '.' '/u' '/usr' '../usr' '/usr' '/u' '../u' "/u'/dir" "/u'/dir" "." "/u'" "/u'/dir" "dir" "/u'/dir" "/u'" ".." "/" "/u'/dir" "u'/dir" "/u'/dir" "/" "../.." "/u'" "/u'" "." "/" "/u'" "u'" "/u'" "/" ".." '/u"/dir' '/u"/dir' '.' '/u"' '/u"/dir' 'dir' '/u"/dir' '/u"' '..' '/' '/u"/dir' 'u"/dir' '/u"/dir' '/' '../..' '/u"' '/u"' '.' '/' '/u"' 'u"' '/u"' '/' '..' '/u /dir' '/u /dir' '.' '/u ' '/u /dir' 'dir' '/u /dir' '/u ' '..' '/' '/u /dir' 'u /dir' '/u /dir' '/' '../..' '/u ' '/u ' '.' '/' '/u ' 'u ' '/u ' '/' '..' '/u\n/dir' '/u\n/dir' '.' '/u\n' '/u\n/dir' 'dir' '/u\n/dir' '/u\n' '..' '/' '/u\n/dir' 'u\n/dir' '/u\n/dir' '/' '../..' '/u\n' '/u\n' '.' '/' '/u\n' 'u\n' '/u\n' '/' '..' '/ a b/?/?*/!' '/ a b/?/?/x??/?' '../../?/x??/?' '/' '/A' 'A' '/A' '/' '..' '/ & / !/*/\/E' '/' '../../../../..' '/' '/ & / !/*/\/E' ' & / !/*/\/E' '/ & / !/*/\/E' '/ & / !/?/\/E/F' '../../../?/\/E/F' '/X/Y' '/ & / !/C/\/E/F' '../../ & / !/C/\/E/F' '/ & / !/C' '/A' '../../../A' '/A / !/C' '/A /B' '../../B' '/?/ !/C' '/?/ !/C' '.' '/ & /B / C' '/ & /B / C/D' 'D' '/ & / !/C' '/ & / !/C/\/ê' '\/ê' '/?/ !/C' '/?/ !/D' '../D' '/.A /*B/C' '/.A /*B/\/E' '../\/E' '/ & / !/C' '/ & /D' '../../D' '/ & / !/C' '/ & /\/E' '../../\/E' '/ & / !/C' '/\/E/F' '../../../\/E/F' '/home/p1/p2' '/home/p1/p3' '../p3' '/home/p1/p2' '/home/p4/p5' '../../p4/p5' '/home/p1/p2' '/work/p6/p7' '../../../work/p6/p7' '/home/p1' '/work/p1/p2/p3/p4' '../../work/p1/p2/p3/p4' '/home' '/work/p2/p3' '../work/p2/p3' '/' '/work/p2/p3/p4' 'work/p2/p3/p4' '/home/p1/p2' '/home/p1/p2/p3/p4' 'p3/p4' '/home/p1/p2' '/home/p1/p2/p3' 'p3' '/home/p1/p2' '/home/p1/p2' '.' '/home/p1/p2' '/home/p1' '..' '/home/p1/p2' '/home' '../..' '/home/p1/p2' '/' '../../..' '/home/p1/p2' '/work' '../../../work' '/home/p1/p2' '/work/p1' '../../../work/p1' '/home/p1/p2' '/work/p1/p2' '../../../work/p1/p2' '/home/p1/p2' '/work/p1/p2/p3' '../../../work/p1/p2/p3' '/home/p1/p2' '/work/p1/p2/p3/p4' '../../../work/p1/p2/p3/p4' '/-' '/-' '.' '/?' '/?' '.' '/??' '/??' '.' '/???' '/???' '.' '/?*' '/?*' '.' '/*' '/*' '.' '/*' '/**' '../**' '/*' '/***' '../***' '/*.*' '/*.**' '../*.**' '/*.???' '/*.??' '../*.??' '/[]' '/[]' '.' '/[a-z]*' '/[0-9]*' '../[0-9]*' EOF format="\t%-19s %-22s %-27s %-8s %-8s\n" printf "\n\n*** Testing own and python's function with arbitrary dirs\n\n" printf "$format\n" \ "From Directory" "To Directory" "Rel. Path" "relpath" "python" IFS= while read -r p; do eval set -- $p case in '#'*|'') continue;; esac # Skip comments and empty lines # q stores quoting character, use " if ' is used in path name q="'"; case in *"'"*) q='"';; esac rpOk=passed rp=$(relpath "" ""); test "$rp" = "" || rpOk=$rp RPOk=passed RP=$(python -c "import os.path; print os.path.relpath($q$q, $q$q)") test "$RP" = "" || RPOk=$RP printf "$format" "$q$q" "$q$q" "$q$q" "$q$rpOk$q" "$q$RPOk$q" done <<-"EOF" # From directory To directory Expected relative path 'usr/p1/..//./p4' 'p3/../p1/p6/.././/p2' '../../p1/p2' './home/../../work' '..//././../dir///' '../../dir' 'home/p1/p2' 'home/p1/p3' '../p3' 'home/p1/p2' 'home/p4/p5' '../../p4/p5' 'home/p1/p2' 'work/p6/p7' '../../../work/p6/p7' 'home/p1' 'work/p1/p2/p3/p4' '../../work/p1/p2/p3/p4' 'home' 'work/p2/p3' '../work/p2/p3' '.' 'work/p2/p3' 'work/p2/p3' 'home/p1/p2' 'home/p1/p2/p3/p4' 'p3/p4' 'home/p1/p2' 'home/p1/p2/p3' 'p3' 'home/p1/p2' 'home/p1/p2' '.' 'home/p1/p2' 'home/p1' '..' 'home/p1/p2' 'home' '../..' 'home/p1/p2' '.' '../../..' 'home/p1/p2' 'work' '../../../work' 'home/p1/p2' 'work/p1' '../../../work/p1' 'home/p1/p2' 'work/p1/p2' '../../../work/p1/p2' 'home/p1/p2' 'work/p1/p2/p3' '../../../work/p1/p2/p3' 'home/p1/p2' 'work/p1/p2/p3/p4' '../../../work/p1/p2/p3/p4' EOF
使用此方法创建的同一目录中的符号链接不会
"./"
在文件名前加上丑陋的内容。
- 如果您发现错误,请联系 linuxball (at) gmail.com,我会尝试修复它。
- 添加了回归测试套件(也兼容 POSIX shell)
Code listing for regression tests (simply append it to the shell script):
回归测试的代码清单(只需将其附加到 shell 脚本):
function relpath() {
python -c "import os,sys;print(os.path.relpath(*(sys.argv[1:])))" "$@";
}
回答by Gary Wisniewski
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):
这里没有很多答案适用于日常使用。由于在纯 bash 中很难正确执行此操作,因此我建议采用以下可靠的解决方案(类似于隐藏在评论中的一个建议):
echo $(relpath somepath)
Then, you can get the relative path based upon the current directory:
然后,您可以根据当前目录获取相对路径:
echo $(relpath somepath /etc) # relative to /etc
or you can specify that the path be relative to a given directory:
或者您可以指定路径相对于给定目录:
absolute="/foo/bar"
current="/foo/baz/foo"
# Perl is magic
relative=$(perl -MFile::Spec -e 'print File::Spec->abs2rel("'$absolute'","'$current'")')
The one disadvantage is this requires python, but:
一个缺点是这需要 python,但是:
- It works identically in any python >= 2.6
- It does not require that the files or directories exist.
- Filenames may contain a wider range of special characters. For example, many other solutions do not work if filenames contain spaces or other special characters.
- It is a one-line function that doesn't clutter scripts.
- 它在任何 python >= 2.6 中的工作方式相同
- 它不要求文件或目录存在。
- 文件名可能包含更广泛的特殊字符。例如,如果文件名包含空格或其他特殊字符,许多其他解决方案将不起作用。
- 它是一个单行函数,不会使脚本混乱。
Note that solutions which include basename
or dirname
may not necessarily be better, as they require that coreutils
be installed. If somebody has a pure bash
solution that is reliable and simple (rather than a convoluted curiosity), I'd be surprised.
请注意,包含basename
或dirname
不一定更好的解决方案,因为它们需要coreutils
安装。如果有人有一个bash
可靠且简单的纯粹解决方案(而不是令人费解的好奇心),我会感到惊讶。
回答by Gary Wisniewski
I would just use Perl for this not-so-trivial task:
我只想使用 Perl 来完成这个不那么简单的任务:
##代码##