उदाहरण:
absolute="/foo/bar"
current="/foo/baz/foo"
# Magic
relative="../../bar"
मैं जादू कैसे बनाऊंगा (उम्मीद है कि बहुत जटिल कोड नहीं है ...)?
realpath --relative-to=$absolute $current
।
उदाहरण:
absolute="/foo/bar"
current="/foo/baz/foo"
# Magic
relative="../../bar"
मैं जादू कैसे बनाऊंगा (उम्मीद है कि बहुत जटिल कोड नहीं है ...)?
realpath --relative-to=$absolute $current
।
जवाबों:
GNU कोरुटिल्स 8.23 से रियलपथ का उपयोग करना सबसे सरल है, मुझे लगता है:
$ realpath --relative-to="$file1" "$file2"
उदाहरण के लिए:
$ realpath --relative-to=/usr/bin/nmap /tmp/testing
../../../tmp/testing
$ realpath --relative-to="${PWD}" "$file"
यदि आप वर्तमान कार्यशील निर्देशिका के सापेक्ष पथ चाहते हैं तो उपयोगी है।
/usr/bin/nmap/
-path लेकिन नहीं करने के लिए /usr/bin/nmap
से: nmap
करने के लिए /tmp/testing
यह केवल ../../
और नहीं 3 बार ../
। यह हालांकि काम करता है, क्योंकि ..
रूटफुट पर करना है /
।
--relative-to=…
एक निर्देशिका की उम्मीद है और जाँच नहीं करता है। इसका मतलब है कि आप एक अतिरिक्त "../" के साथ समाप्त होते हैं यदि आप किसी फ़ाइल के सापेक्ष पथ का अनुरोध करते हैं (जैसा कि यह उदाहरण प्रतीत होता है, क्योंकि /usr/bin
शायद ही कभी या कभी निर्देशिकाएं नहीं होती हैं और nmap
सामान्य रूप से द्विआधारी होती है)
$ python -c "import os.path; print os.path.relpath('/foo/bar', '/foo/baz/foo')"
देता है:
../../bar
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:]))'
सबसे स्वाभाविक रूप से और मज़बूती से काम करता है।
यह @pini से वर्तमान में सबसे अच्छा रेटेड समाधान का एक सही, पूरी तरह कार्यात्मक सुधार है (जो दुख की बात है केवल कुछ मामलों को संभालता है)
अनुस्मारक: '-z' परीक्षण अगर स्ट्रिंग शून्य-लंबाई (= खाली) और '-n' परीक्षण है यदि स्ट्रिंग खाली नहीं है।
# both $1 and $2 are absolute paths beginning with /
# returns relative path to $2/$target from $1/$source
source=$1
target=$2
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
परीक्षण के मामलों :
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"
source=$1; target=$2
साथsource=$(realpath $1); target=$(realpath $2)
realpath
की सिफारिश की जाती है, या source=$(readlink -f $1)
आदि यदि
$source
और इसे $target
पसंद किया: `अगर [[-ई $ 1]]; तब स्रोत = $ (रीडलिंक -f $ 1); और स्रोत = $ 1; फाई अगर [[-ई $ 2]]; फिर लक्ष्य = $ (रीडलिंक -f $ 2); और लक्ष्य = $ 2; Fi` इस तरह, फ़ंक्शन वास्तविक / मौजूदा पथों के साथ-साथ काल्पनिक निर्देशिकाओं को भी छोटा कर सकता है।
readlink
में खोजा एक -m
विकल्प है जो अभी-अभी करता है;)
#!/bin/bash
# both $1 and $2 are absolute paths
# returns $2 relative to $1
source=$1
target=$2
common_part=$source
back=
while [ "${target#$common_part}" = "${target}" ]; do
common_part=$(dirname $common_part)
back="../${back}"
done
echo ${back}${target#$common_part/}
यह 2001 से पर्ल में बनाया गया है , इसलिए यह लगभग हर प्रणाली पर काम करता है जिसकी आप कल्पना कर सकते हैं, यहां तक कि वीएमएस भी ।
perl -e 'use File::Spec; print File::Spec->abs2rel(@ARGV) . "\n"' FILE BASE
इसके अलावा, समाधान को समझना आसान है।
तो आपके उदाहरण के लिए:
perl -e 'use File::Spec; print File::Spec->abs2rel(@ARGV) . "\n"' $absolute $current
... ठीक काम करेगा।
say
लॉग के रूप में पर्ल में उपलब्ध नहीं है, लेकिन इसका उपयोग यहां प्रभावी रूप से किया जा सकता है। perl -MFile::Spec -E 'say File::Spec->abs2rel(@ARGV)'
perl -MFile::Spec -e 'print File::Spec->abs2rel(@ARGV)' "$target"
और perl -MFile::Spec -e 'print File::Spec->abs2rel(@ARGV)' "$target" "$origin"
। पहली एक-लाइन पर्ल स्क्रिप्ट एक तर्क का उपयोग करती है (मूल वर्तमान कार्यशील निर्देशिका है)। दूसरी एक-लाइन पर्ल स्क्रिप्ट दो तर्कों का उपयोग करती है।
perl
लगभग हर जगह पाया जा सकता है, हालांकि जवाब अभी भी एक-लाइनर है।
यह मानते हुए कि आपने स्थापित किया है: बैश, पीडब्ल्यूडी, डिरनेम, इको; फिर रिलेपाथ है
#!/bin/bash
s=$(cd ${1%%/};pwd); d=$(cd $2;pwd); b=; while [ "${d#$s/}" == "${d}" ]
do s=$(dirname $s);b="../${b}"; done; echo ${b}${d#$s/}
मैं से जवाब golfed गए Pini और कुछ अन्य विचारों
नोट : इसके लिए मौजूदा फ़ोल्डर के लिए दोनों रास्तों की आवश्यकता होती है। फाइलें काम नहीं करेंगी।
os.path.relpath
एक शेल फ़ंक्शन के रूप मेंइस relpath
अभ्यास का लक्ष्य पायथन 2.7 के os.path.relpath
कार्य की नकल करना है (पायथन संस्करण 2.6 से उपलब्ध है, लेकिन केवल 2.7 में ठीक से काम कर रहा है), जैसा कि xni द्वारा प्रस्तावित है । परिणामस्वरूप, कुछ परिणाम अन्य उत्तरों में दिए गए कार्यों से भिन्न हो सकते हैं।
(मैंने रास्तों में newlines के साथ परीक्षण नहीं किया है क्योंकि यह python -c
ZSH से कॉल करने के आधार पर मान्यता को तोड़ता है । यह निश्चित रूप से कुछ प्रयासों के साथ संभव होगा।)
बाश में "जादू" के बारे में, मैंने बहुत पहले बाश में जादू की तलाश छोड़ दी है, लेकिन मैंने तब से सभी जादू की जरूरत है, और फिर कुछ, ZSH में पाया है।
नतीजतन, मैं दो कार्यान्वयन का प्रस्ताव करता हूं।
पहला कार्यान्वयन पूरी तरह से POSIX- अनुरूप होना है । मैंने /bin/dash
डेबियन 6.0.6 "निचोड़" पर इसका परीक्षण किया है । यह भी पूरी तरह से साथ काम करता है/bin/sh
OS X 10.8.3 पर , जो कि वास्तव में Bash वर्जन 3.2 है, जो कि POSIX शेल है।
दूसरा कार्यान्वयन एक ZSH शेल फ़ंक्शन है जो पथ में कई स्लैश और अन्य उपद्रवों के खिलाफ मजबूत है। यदि आपके पास ज़ेडएस उपलब्ध है, तो यह अनुशंसित संस्करण है, भले ही आप इसे #!/usr/bin/env zsh
दूसरे शेल से नीचे दिए गए स्क्रिप्ट फॉर्म में बुला रहे हों (यानी कि शेबग के साथ )।
अंत में, मैंने एक ZSH स्क्रिप्ट लिखी है, जो अन्य उत्तरों relpath
में $PATH
दिए गए परीक्षण मामलों को देखते हुए कमांड के आउटपुट को सत्यापित करती है। मैंने कुछ स्थानों, टैब और विराम चिह्नों को जोड़कर उन परीक्षणों में कुछ मसाला डाला जैसे कि ! ? *
इधर -उधर करना और इसके बाद भी एक और परीक्षण जो एक्सट्रीम UTF-8 वर्णों के साथ vim-powerline में पाया जाता है ।
सबसे पहले, POSIX-compliant शेल फ़ंक्शन। यह कई तरह के रास्तों के साथ काम करता है, लेकिन कई स्लैश को साफ नहीं करता है और न ही सिम्बल को सुलझाता है।
#!/bin/sh
relpath () {
[ $# -ge 1 ] && [ $# -le 2 ] || return 1
current="${2:+"$1"}"
target="${2:-"$1"}"
[ "$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
संस्करण। यदि आप यह चाहते हैं कि तर्कों को वास्तविक ला realpath -f
(लिनक्स coreutils
पैकेज में उपलब्ध) में हल किया जाए , तो :a
ऑन लाइन 3 और 4 को बदल दें :A
।
Zsh में इसका उपयोग करने के लिए, पहली और अंतिम पंक्ति को हटा दें और इसे एक निर्देशिका में रखें जो आपके $FPATH
चर में है।
#!/usr/bin/env zsh
relpath () {
[[ $# -ge 1 ]] && [[ $# -le 2 ]] || return 1
local target=${${2:-$1}:a} # replace `:a' by `:A` to resolve symlinks
local current=${${${2:+$1}:-$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 "$@"
अंत में, परीक्षण स्क्रिप्ट। यह क्रिया विकल्प -v
को सक्षम करने के लिए एक विकल्प को स्वीकार करता है ।
#!/usr/bin/env zsh
set -eu
VERBOSE=false
script_name=$(basename $0)
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:-$1}}
prefix=${${${2:+$1}:-$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 ]] && [[ $1 = "-v" ]]; then
VERBOSE=true
shift
fi
if [[ $# -eq 0 ]]; then
run_checks
else
VERBOSE=true
relpath_check "$@"
fi
/
मुझे डर नहीं लगता।
#!/bin/sh
# Return relative path from canonical absolute dir path $1 to canonical
# absolute dir path $2 ($1 and/or $2 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 $1 to dir $2 (Does not impose any
# restrictions on $1 and $2 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 "$1")" "$(readlink -m "$2")"; }
शेल स्क्रिप्ट के ऊपर पिनी के द्वारा प्रेरित किया गया था (धन्यवाद!)। यह स्टैक ओवरफ्लो के कम से कम हाइलाइटिंग मॉड्यूल में एक बग को ट्रिगर करता है (कम से कम मेरे पूर्वावलोकन फ्रेम में)। इसलिए कृपया ध्यान दें कि यदि हाइलाइटिंग गलत है।
कुछ नोट:
उल्लिखित बैकस्लैश अनुक्रमों को छोड़कर, फ़ंक्शन के अंतिम पंक्ति "relPath" आउटपुट पैथेमोन के संगत पैथेमोन:
path=$up${path#"$common"/}; path=${path%/}; printf %s "${path:-.}"
अंतिम पंक्ति को लाइन द्वारा प्रतिस्थापित (और सरलीकृत) किया जा सकता है
printf %s "$up${path#"$common"/}"
मैं बाद पसंद करते हैं क्योंकि
फ़ाइल नाम सीधे relpath द्वारा प्राप्त dir पथों के लिए जोड़ा जा सकता है, उदाहरण के लिए:
ln -s "$(relpath "<fromDir>" "<toDir>")<file>" "<fromDir>"
इस विधि के साथ बनाए गए एक ही डायर में प्रतीकात्मक लिंक में "./"
फ़ाइल नाम का बदसूरत हिस्सा नहीं होता है ।
प्रतिगमन परीक्षणों के लिए कोड सूची (केवल इसे शेल स्क्रिप्ट में जोड़ें):
############################################################################
# If called with 2 arguments assume they are dir paths and print rel. path #
############################################################################
test "$#" = 2 && {
printf '%s\n' "Rel. path from '$1' to '$2' is '$(relpath "$1" "$2")'."
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 $1 in '#'*|'') continue;; esac # Skip comments and empty lines
# q stores quoting character, use " if ' is used in path name
q="'"; case $1$2 in *"'"*) q='"';; esac
rPOk=passed rP=$(relPath "$1" "$2"); test "$rP" = "$3" || rPOk=$rP
rpOk=passed rp=$(relpath "$1" "$2"); test "$rp" = "$3" || rpOk=$rp
RPOk=passed
RP=$(python -c "import os.path; print os.path.relpath($q$2$q, $q$1$q)")
test "$RP" = "$3" || RPOk=$RP
printf \
"$format" "$q$1$q" "$q$2$q" "$q$3$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 $1 in '#'*|'') continue;; esac # Skip comments and empty lines
# q stores quoting character, use " if ' is used in path name
q="'"; case $1$2 in *"'"*) q='"';; esac
rpOk=passed rp=$(relpath "$1" "$2"); test "$rp" = "$3" || rpOk=$rp
RPOk=passed
RP=$(python -c "import os.path; print os.path.relpath($q$2$q, $q$1$q)")
test "$RP" = "$3" || RPOk=$RP
printf "$format" "$q$1$q" "$q$2$q" "$q$3$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
यहां बहुत सारे उत्तर हर दिन उपयोग के लिए व्यावहारिक नहीं हैं। चूंकि शुद्ध बैश में इसे ठीक से करना बहुत मुश्किल है, इसलिए मैं निम्नलिखित, विश्वसनीय समाधान सुझाता हूं (एक टिप्पणी में दफन एक सुझाव के समान):
function relpath() {
python -c "import os,sys;print(os.path.relpath(*(sys.argv[1:])))" "$@";
}
फिर, आप वर्तमान निर्देशिका के आधार पर सापेक्ष पथ प्राप्त कर सकते हैं:
echo $(relpath somepath)
या आप यह निर्दिष्ट कर सकते हैं कि पथ किसी दिए गए निर्देशिका के सापेक्ष है:
echo $(relpath somepath /etc) # relative to /etc
एक नुकसान यह है कि अजगर की आवश्यकता है, लेकिन:
ध्यान दें कि जो समाधान शामिल हैं basename
या dirname
जरूरी नहीं कि वे बेहतर हों, क्योंकि उन्हें coreutils
स्थापित करने की आवश्यकता होती है । यदि किसी के पास एक शुद्ध bash
समाधान है जो विश्वसनीय और सरल है (एक दृढ़ जिज्ञासा के बजाय), तो मुझे आश्चर्य होगा।
यह स्क्रिप्ट केवल उन इनपुट के लिए सही परिणाम देती है जो बिना .
या उसके पूर्ण पथ या सापेक्ष पथ हैं ..
:
#!/bin/bash
# usage: relpath from to
if [[ "$1" == "$2" ]]
then
echo "."
exit
fi
IFS="/"
current=($1)
absolute=($2)
abssize=${#absolute[@]}
cursize=${#current[@]}
while [[ ${absolute[level]} == ${current[level]} ]]
do
(( level++ ))
if (( level > abssize || level > cursize ))
then
break
fi
done
for ((i = level; i < cursize; i++))
do
if ((i > level))
then
newpath=$newpath"/"
fi
newpath=$newpath".."
done
for ((i = level; i < abssize; i++))
do
if [[ -n $newpath ]]
then
newpath=$newpath"/"
fi
newpath=$newpath${absolute[i]}
done
echo "$newpath"
मैं सिर्फ इस गैर-तुच्छ कार्य के लिए पर्ल का उपयोग करूंगा:
absolute="/foo/bar"
current="/foo/baz/foo"
# Perl is magic
relative=$(perl -MFile::Spec -e 'print File::Spec->abs2rel("'$absolute'","'$current'")')
perl -MFile::Spec -e "print File::Spec->abs2rel('$absolute','$current')"
ताकि निरपेक्ष और वर्तमान उद्धृत हो।
relative=$(perl -MFile::Spec -e 'print File::Spec->abs2rel(@ARGV)' "$absolute" "$current")
। यह सुनिश्चित करता है कि मान स्वयं नहीं हो सकते, जिसमें पर्ल कोड हो!
कास्कू और पिनी के उत्तरों पर एक मामूली सुधार , जो रिक्त स्थान के साथ अच्छा खेलता है और रिश्तेदार पथों को पारित करने की अनुमति देता है:
#!/bin/bash
# both $1 and $2 are paths
# returns $2 relative to $1
absolute=`readlink -f "$2"`
current=`readlink -f "$1"`
# Perl is magic
# Quoting horror.... spaces cause problems, that's why we need the extra " in here:
relative=$(perl -MFile::Spec -e "print File::Spec->abs2rel(q($absolute),q($current))")
echo $relative
test.sh:
#!/bin/bash
cd /home/ubuntu
touch blah
TEST=/home/ubuntu/.//blah
echo TEST=$TEST
TMP=$(readlink -e "$TEST")
echo TMP=$TMP
REL=${TMP#$(pwd)/}
echo REL=$REL
परिक्षण:
$ ./test.sh
TEST=/home/ubuntu/.//blah
TMP=/home/ubuntu/blah
REL=blah
readlink
पर $(pwd)
।
फिर भी एक और समाधान, शुद्ध bash
+ GNU readlink
निम्नलिखित संदर्भ में आसान उपयोग के लिए:
ln -s "$(relpath "$A" "$B")" "$B"
संपादित करें: सुनिश्चित करें कि "$ B" या तो मौजूदा नहीं है या उस मामले में कोई सॉफ्टलिंक नहीं है, अन्यथा
relpath
इस लिंक का अनुसरण करता है जो आप नहीं चाहते हैं!
यह लगभग सभी वर्तमान लिनक्स में काम करता है। यदि readlink -m
आपके पक्ष में काम नहीं करता है, तो readlink -f
इसके बजाय प्रयास करें । यह भी देखें : https://gist.github.com/hilbix/1ec361d00a8178ae8ea0 संभव के लिए देखें :
: relpath A B
# Calculate relative path from A to B, returns true on success
# Example: ln -s "$(relpath "$A" "$B")" "$B"
relpath()
{
local X Y A
# We can create dangling softlinks
X="$(readlink -m -- "$1")" || return
Y="$(readlink -m -- "$2")" || return
X="${X%/}/"
A=""
while Y="${Y%/*}"
[ ".${X#"$Y"/}" = ".$X" ]
do
A="../$A"
done
X="$A${X#"$Y"/}"
X="${X%/}"
echo "${X:-.}"
}
टिप्पणियाँ:
*
या है ?
।ln -s
:
relpath / /
.
खाली स्ट्रिंग देता है और नहींrelpath a a
एक निर्देशिका होने के a
बावजूद भी देता हैa
readlink
कैनोनिकलाइज़ पथों की आवश्यकता होती है।readlink -m
मौजूदा पथों के लिए भी काम करता है, भी।पुरानी प्रणालियों पर, जहां readlink -m
उपलब्ध नहीं है, readlink -f
यदि फ़ाइल मौजूद नहीं है, तो विफल रहता है। तो आपको शायद इस तरह से कुछ वर्कअराउंड की आवश्यकता है!
readlink_missing()
{
readlink -m -- "$1" && return
readlink -f -- "$1" && return
[ -e . ] && echo "$(readlink_missing "$(dirname "$1")")/$(basename "$1")"
}
यह वास्तव में मामले $1
में काफी सही नहीं है .
या इसमें किसी भी ..
तरह के पथ शामिल नहीं हैं (जैसे /doesnotexist/./a
), लेकिन इसे अधिकांश मामलों को कवर करना चाहिए।
( readlink -m --
ऊपर से बदलें readlink_missing
)
यहाँ एक परीक्षण है, कि यह फ़ंक्शन, वास्तव में, सही है:
check()
{
res="$(relpath "$2" "$1")"
[ ".$res" = ".$3" ] && return
printf ':WRONG: %-10q %-10q gives %q\nCORRECT %-10q %-10q gives %q\n' "$1" "$2" "$res" "$@"
}
# TARGET SOURCE RESULT
check "/A/B/C" "/A" ".."
check "/A/B/C" "/A.x" "../../A.x"
check "/A/B/C" "/A/B" "."
check "/A/B/C" "/A/B/C" "C"
check "/A/B/C" "/A/B/C/D" "C/D"
check "/A/B/C" "/A/B/C/D/E" "C/D/E"
check "/A/B/C" "/A/B/D" "D"
check "/A/B/C" "/A/B/D/E" "D/E"
check "/A/B/C" "/A/D" "../D"
check "/A/B/C" "/A/D/E" "../D/E"
check "/A/B/C" "/D/E/F" "../../D/E/F"
check "/foo/baz/moo" "/foo/bar" "../bar"
हैरान? खैर, ये सही परिणाम हैं ! यहां तक कि अगर आपको लगता है कि यह सवाल फिट नहीं बैठता है, तो यहां सबूत है कि यह सही है:
check "http://example.com/foo/baz/moo" "http://example.com/foo/bar" "../bar"
बिना किसी संदेह के, ../bar
पृष्ठ से bar
देखे गए पृष्ठ का सटीक और एकमात्र सही मार्ग है moo
। बाकी सब गलत होगा।
यह उत्पादन को उस प्रश्न को अपनाने के लिए तुच्छ है जो स्पष्ट रूप से मानता है, current
यह एक निर्देशिका है:
absolute="/foo/bar"
current="/foo/baz/foo"
relative="../$(relpath "$absolute" "$current")"
यह वही देता है, जो मांगा गया था।
और इससे पहले कि आप एक भौं उठाएं, यहां थोड़ा और अधिक जटिल संस्करण relpath
(छोटा अंतर हाजिर) है, जो URL-सिंटैक्स के लिए भी काम करना चाहिए, (इसलिए एक अनुगामी /
जीवित रहता है, कुछ- bash
सदिश के लिए धन्यवाद ):
# Calculate relative PATH to the given DEST from the given BASE
# In the URL case, both URLs must be absolute and have the same Scheme.
# The `SCHEME:` must not be present in the FS either.
# This way this routine works for file paths an
: relpathurl DEST BASE
relpathurl()
{
local X Y A
# We can create dangling softlinks
X="$(readlink -m -- "$1")" || return
Y="$(readlink -m -- "$2")" || return
X="${X%/}/${1#"${1%/}"}"
Y="${Y%/}${2#"${2%/}"}"
A=""
while Y="${Y%/*}"
[ ".${X#"$Y"/}" = ".$X" ]
do
A="../$A"
done
X="$A${X#"$Y"/}"
X="${X%/}"
echo "${X:-.}"
}
और यहाँ चेक केवल स्पष्ट करने के लिए हैं: यह वास्तव में बताए अनुसार काम करता है।
check()
{
res="$(relpathurl "$2" "$1")"
[ ".$res" = ".$3" ] && return
printf ':WRONG: %-10q %-10q gives %q\nCORRECT %-10q %-10q gives %q\n' "$1" "$2" "$res" "$@"
}
# TARGET SOURCE RESULT
check "/A/B/C" "/A" ".."
check "/A/B/C" "/A.x" "../../A.x"
check "/A/B/C" "/A/B" "."
check "/A/B/C" "/A/B/C" "C"
check "/A/B/C" "/A/B/C/D" "C/D"
check "/A/B/C" "/A/B/C/D/E" "C/D/E"
check "/A/B/C" "/A/B/D" "D"
check "/A/B/C" "/A/B/D/E" "D/E"
check "/A/B/C" "/A/D" "../D"
check "/A/B/C" "/A/D/E" "../D/E"
check "/A/B/C" "/D/E/F" "../../D/E/F"
check "/foo/baz/moo" "/foo/bar" "../bar"
check "http://example.com/foo/baz/moo" "http://example.com/foo/bar" "../bar"
check "http://example.com/foo/baz/moo/" "http://example.com/foo/bar" "../../bar"
check "http://example.com/foo/baz/moo" "http://example.com/foo/bar/" "../bar/"
check "http://example.com/foo/baz/moo/" "http://example.com/foo/bar/" "../../bar/"
और यहां बताया गया है कि प्रश्न से वांछित परिणाम देने के लिए इसका उपयोग कैसे किया जा सकता है:
absolute="/foo/bar"
current="/foo/baz/foo"
relative="$(relpathurl "$absolute" "$current/")"
echo "$relative"
अगर आपको कोई ऐसी चीज़ मिले जो काम नहीं करती है, तो कृपया मुझे नीचे टिप्पणी में बताएं। धन्यवाद।
पुनश्च:
relpath
यहाँ अन्य सभी उत्तरों के विपरीत "उलट" के तर्क क्यों हैं ?
अगर तुम बदलो
Y="$(readlink -m -- "$2")" || return
सेवा
Y="$(readlink -m -- "${2:-"$PWD"}")" || return
फिर आप 2 पैरामीटर को छोड़ सकते हैं, जैसे कि BASE वर्तमान निर्देशिका / URL / जो भी हो। यह केवल यूनिक्स सिद्धांत है, हमेशा की तरह।
यदि आपको वह नापसंद है, तो कृपया Windows पर वापस जाएं। धन्यवाद।
अफसोस की बात है कि मार्क रुशकॉफ़ का जवाब (अब हटा दिया गया है) ने इसे कोड से संदर्भित किया यहां है ) सही तरीके से काम नहीं करता है:
source=/home/part2/part3/part4
target=/work/proj1/proj2
कमेंट्री में बताई गई सोच को ज्यादातर मामलों के लिए सही ढंग से काम करने के लिए परिष्कृत किया जा सकता है। मैं यह मानने वाला हूं कि स्क्रिप्ट एक स्रोत तर्क (जहां आप हैं) और एक लक्ष्य तर्क (जहां आप प्राप्त करना चाहते हैं) को लेती है, और यह कि दोनों पूर्ण पथ प्रदर्शक हैं या दोनों सापेक्ष हैं। यदि कोई निरपेक्ष है और दूसरा सापेक्ष है, तो सबसे आसान काम है कि वर्तमान कार्य निर्देशिका के सापेक्ष नाम को उपसर्ग करना - लेकिन नीचे दिया गया कोड ऐसा नहीं करता है।
नीचे दिया गया कोड सही ढंग से काम करने के करीब है, लेकिन काफी सही नहीं है।
xyz/./pqr
' ।xyz/../pqr
' ।./
पथ से प्रमुख ' ' को नहीं हटाता है।डेनिस का कोड बेहतर है क्योंकि यह 1 और 5 को ठीक करता है - लेकिन इसमें 2, 3, 4 के समान मुद्दे हैं। डेनिस के कोड का उपयोग करें (और इसके आगे इसे वोट करें)।
(NB: POSIX एक सिस्टम कॉल प्रदान करता है realpath()
जो पथनामों को हल करता है ताकि उनमें कोई सहानुभूति न रह जाए। इनपुट नामों पर लागू होता है, और फिर डेनिस कोड का उपयोग करके प्रत्येक बार सही उत्तर दिया जाएगा। C कोड लिखना तुच्छ है। रैप्स realpath()
- मैंने कर लिया है - लेकिन मुझे एक मानक उपयोगिता का पता नहीं है जो ऐसा करता है।)
इसके लिए, मुझे शेल की तुलना में पर्ल का उपयोग करना आसान लगता है, हालांकि बैश में सरणियों के लिए अच्छा समर्थन है और शायद यह भी कर सकता है - पाठक के लिए व्यायाम। इसलिए, दो संगत नाम दिए गए हैं, उन्हें प्रत्येक घटक में विभाजित करें:
इस प्रकार:
#!/bin/perl -w
use strict;
# Should fettle the arguments if one is absolute and one relative:
# Oops - missing functionality!
# Split!
my(@source) = split '/', $ARGV[0];
my(@target) = split '/', $ARGV[1];
my $count = scalar(@source);
$count = scalar(@target) if (scalar(@target) < $count);
my $relpath = "";
my $i;
for ($i = 0; $i < $count; $i++)
{
last if $source[$i] ne $target[$i];
}
$relpath = "." if ($i >= scalar(@source) && $relpath eq "");
for (my $s = $i; $s < scalar(@source); $s++)
{
$relpath = "../$relpath";
}
$relpath = "." if ($i >= scalar(@target) && $relpath eq "");
for (my $t = $i; $t < scalar(@target); $t++)
{
$relpath .= "/$target[$t]";
}
# Clean up result (remove double slash, trailing slash, trailing slash-dot).
$relpath =~ s%//%/%;
$relpath =~ s%/$%%;
$relpath =~ s%/\.$%%;
print "source = $ARGV[0]\n";
print "target = $ARGV[1]\n";
print "relpath = $relpath\n";
टेस्ट स्क्रिप्ट (वर्ग कोष्ठक में एक रिक्त और एक टैब होता है):
sed 's/#.*//;/^[ ]*$/d' <<! |
/home/part1/part2 /home/part1/part3
/home/part1/part2 /home/part4/part5
/home/part1/part2 /work/part6/part7
/home/part1 /work/part1/part2/part3/part4
/home /work/part2/part3
/ /work/part2/part3/part4
/home/part1/part2 /home/part1/part2/part3/part4
/home/part1/part2 /home/part1/part2/part3
/home/part1/part2 /home/part1/part2
/home/part1/part2 /home/part1
/home/part1/part2 /home
/home/part1/part2 /
/home/part1/part2 /work
/home/part1/part2 /work/part1
/home/part1/part2 /work/part1/part2
/home/part1/part2 /work/part1/part2/part3
/home/part1/part2 /work/part1/part2/part3/part4
home/part1/part2 home/part1/part3
home/part1/part2 home/part4/part5
home/part1/part2 work/part6/part7
home/part1 work/part1/part2/part3/part4
home work/part2/part3
. work/part2/part3
home/part1/part2 home/part1/part2/part3/part4
home/part1/part2 home/part1/part2/part3
home/part1/part2 home/part1/part2
home/part1/part2 home/part1
home/part1/part2 home
home/part1/part2 .
home/part1/part2 work
home/part1/part2 work/part1
home/part1/part2 work/part1/part2
home/part1/part2 work/part1/part2/part3
home/part1/part2 work/part1/part2/part3/part4
!
while read source target
do
perl relpath.pl $source $target
echo
done
परीक्षण स्क्रिप्ट से आउटपुट:
source = /home/part1/part2
target = /home/part1/part3
relpath = ../part3
source = /home/part1/part2
target = /home/part4/part5
relpath = ../../part4/part5
source = /home/part1/part2
target = /work/part6/part7
relpath = ../../../work/part6/part7
source = /home/part1
target = /work/part1/part2/part3/part4
relpath = ../../work/part1/part2/part3/part4
source = /home
target = /work/part2/part3
relpath = ../work/part2/part3
source = /
target = /work/part2/part3/part4
relpath = ./work/part2/part3/part4
source = /home/part1/part2
target = /home/part1/part2/part3/part4
relpath = ./part3/part4
source = /home/part1/part2
target = /home/part1/part2/part3
relpath = ./part3
source = /home/part1/part2
target = /home/part1/part2
relpath = .
source = /home/part1/part2
target = /home/part1
relpath = ..
source = /home/part1/part2
target = /home
relpath = ../..
source = /home/part1/part2
target = /
relpath = ../../../..
source = /home/part1/part2
target = /work
relpath = ../../../work
source = /home/part1/part2
target = /work/part1
relpath = ../../../work/part1
source = /home/part1/part2
target = /work/part1/part2
relpath = ../../../work/part1/part2
source = /home/part1/part2
target = /work/part1/part2/part3
relpath = ../../../work/part1/part2/part3
source = /home/part1/part2
target = /work/part1/part2/part3/part4
relpath = ../../../work/part1/part2/part3/part4
source = home/part1/part2
target = home/part1/part3
relpath = ../part3
source = home/part1/part2
target = home/part4/part5
relpath = ../../part4/part5
source = home/part1/part2
target = work/part6/part7
relpath = ../../../work/part6/part7
source = home/part1
target = work/part1/part2/part3/part4
relpath = ../../work/part1/part2/part3/part4
source = home
target = work/part2/part3
relpath = ../work/part2/part3
source = .
target = work/part2/part3
relpath = ../work/part2/part3
source = home/part1/part2
target = home/part1/part2/part3/part4
relpath = ./part3/part4
source = home/part1/part2
target = home/part1/part2/part3
relpath = ./part3
source = home/part1/part2
target = home/part1/part2
relpath = .
source = home/part1/part2
target = home/part1
relpath = ..
source = home/part1/part2
target = home
relpath = ../..
source = home/part1/part2
target = .
relpath = ../../..
source = home/part1/part2
target = work
relpath = ../../../work
source = home/part1/part2
target = work/part1
relpath = ../../../work/part1
source = home/part1/part2
target = work/part1/part2
relpath = ../../../work/part1/part2
source = home/part1/part2
target = work/part1/part2/part3
relpath = ../../../work/part1/part2/part3
source = home/part1/part2
target = work/part1/part2/part3/part4
relpath = ../../../work/part1/part2/part3/part4
यह पर्ल स्क्रिप्ट यूनिक्स पर काफी अच्छी तरह से काम करती है (यह अजीब इनपुट के चेहरे में विंडोज पथ के नाम की सभी जटिलताओं को ध्यान में नहीं रखता है)। यह मौजूद नामों के वास्तविक पथ को हल करने के लिए मॉड्यूल Cwd
और उसके फ़ंक्शन का उपयोग करता realpath
है, और उन पथों के लिए एक पाठीय विश्लेषण करता है जो मौजूद नहीं हैं। एक को छोड़कर सभी मामलों में, यह डेनिस की स्क्रिप्ट के समान आउटपुट का उत्पादन करता है। विचलित मामला है:
source = home/part1/part2
target = .
relpath1 = ../../..
relpath2 = ../../../.
दो परिणाम बराबर हैं - सिर्फ समान नहीं। (आउटपुट टेस्ट स्क्रिप्ट के एक हल्के संशोधित संस्करण से है - नीचे दी गई पर्ल स्क्रिप्ट इनपुट्स और उत्तर की तरह केवल उत्तर का प्रिंट देती है।) अब: क्या मुझे गैर-कार्यशील उत्तर को समाप्त करना चाहिए? शायद...
#!/bin/perl -w
# Based loosely on code from: http://unix.derkeiler.com/Newsgroups/comp.unix.shell/2005-10/1256.html
# Via: http://stackoverflow.com/questions/2564634
use strict;
die "Usage: $0 from to\n" if scalar @ARGV != 2;
use Cwd qw(realpath getcwd);
my $pwd;
my $verbose = 0;
# Fettle filename so it is absolute.
# Deals with '//', '/./' and '/../' notations, plus symlinks.
# The realpath() function does the hard work if the path exists.
# For non-existent paths, the code does a purely textual hack.
sub resolve
{
my($name) = @_;
my($path) = realpath($name);
if (!defined $path)
{
# Path does not exist - do the best we can with lexical analysis
# Assume Unix - not dealing with Windows.
$path = $name;
if ($name !~ m%^/%)
{
$pwd = getcwd if !defined $pwd;
$path = "$pwd/$path";
}
$path =~ s%//+%/%g; # Not UNC paths.
$path =~ s%/$%%; # No trailing /
$path =~ s%/\./%/%g; # No embedded /./
# Try to eliminate /../abc/
$path =~ s%/\.\./(?:[^/]+)(/|$)%$1%g;
$path =~ s%/\.$%%; # No trailing /.
$path =~ s%^\./%%; # No leading ./
# What happens with . and / as inputs?
}
return($path);
}
sub print_result
{
my($source, $target, $relpath) = @_;
if ($verbose)
{
print "source = $ARGV[0]\n";
print "target = $ARGV[1]\n";
print "relpath = $relpath\n";
}
else
{
print "$relpath\n";
}
exit 0;
}
my($source) = resolve($ARGV[0]);
my($target) = resolve($ARGV[1]);
print_result($source, $target, ".") if ($source eq $target);
# Split!
my(@source) = split '/', $source;
my(@target) = split '/', $target;
my $count = scalar(@source);
$count = scalar(@target) if (scalar(@target) < $count);
my $relpath = "";
my $i;
# Both paths are absolute; Perl splits an empty field 0.
for ($i = 1; $i < $count; $i++)
{
last if $source[$i] ne $target[$i];
}
for (my $s = $i; $s < scalar(@source); $s++)
{
$relpath = "$relpath/" if ($s > $i);
$relpath = "$relpath..";
}
for (my $t = $i; $t < scalar(@target); $t++)
{
$relpath = "$relpath/" if ($relpath ne "");
$relpath = "$relpath$target[$t]";
}
print_result($source, $target, $relpath);
/home/part1/part2
करने के लिए /
एक बहुत अधिक है ../
। अन्यथा, मेरी स्क्रिप्ट आपके आउटपुट से मेल खाती है, मेरे अलावा .
जहां गंतव्य है, उसके अंत में एक अनावश्यक जोड़ता है .
और मैं उन ./
लोगों की शुरुआत में उपयोग नहीं करता हूं जो ऊपर जाने के बिना उतरते हैं।
readlink /usr/bin/vi
देता है /etc/alternatives/vi
, लेकिन वह एक और सिम्कलिन है - जबकि readlink -f /usr/bin/vi
देता है /usr/bin/vim.basic
, जो सभी
मैंने आपके प्रश्न को "पोर्टेबल" शेल कोड में लिखने की चुनौती के रूप में लिया, अर्थात
यह किसी भी POSIX अनुरूप शेल (zsh, bash, ksh, ash, busybox, ...) पर चलता है। यहां तक कि इसके संचालन को सत्यापित करने के लिए एक टेस्टसाइट भी शामिल है। रोगनामों के कैनेलाइज़ेशन को एक अभ्यास के रूप में छोड़ दिया जाता है। :-)
#!/bin/sh
# Find common parent directory path for a pair of paths.
# Call with two pathnames as args, e.g.
# commondirpart foo/bar foo/baz/bat -> result="foo/"
# The result is either empty or ends with "/".
commondirpart () {
result=""
while test ${#1} -gt 0 -a ${#2} -gt 0; do
if test "${1%${1#?}}" != "${2%${2#?}}"; then # First characters the same?
break # No, we're done comparing.
fi
result="$result${1%${1#?}}" # Yes, append to result.
set -- "${1#?}" "${2#?}" # Chop first char off both strings.
done
case "$result" in
(""|*/) ;;
(*) result="${result%/*}/";;
esac
}
# Turn foo/bar/baz into ../../..
#
dir2dotdot () {
OLDIFS="$IFS" IFS="/" result=""
for dir in $1; do
result="$result../"
done
result="${result%/}"
IFS="$OLDIFS"
}
# Call with FROM TO args.
relativepath () {
case "$1" in
(*//*|*/./*|*/../*|*?/|*/.|*/..)
printf '%s\n' "'$1' not canonical"; exit 1;;
(/*)
from="${1#?}";;
(*)
printf '%s\n' "'$1' not absolute"; exit 1;;
esac
case "$2" in
(*//*|*/./*|*/../*|*?/|*/.|*/..)
printf '%s\n' "'$2' not canonical"; exit 1;;
(/*)
to="${2#?}";;
(*)
printf '%s\n' "'$2' not absolute"; exit 1;;
esac
case "$to" in
("$from") # Identical directories.
result=".";;
("$from"/*) # From /x to /x/foo/bar -> foo/bar
result="${to##$from/}";;
("") # From /foo/bar to / -> ../..
dir2dotdot "$from";;
(*)
case "$from" in
("$to"/*) # From /x/foo/bar to /x -> ../..
dir2dotdot "${from##$to/}";;
(*) # Everything else.
commondirpart "$from" "$to"
common="$result"
dir2dotdot "${from#$common}"
result="$result/${to#$common}"
esac
;;
esac
}
set -f # noglob
set -x
cat <<EOF |
/ / .
/- /- .
/? /? .
/?? /?? .
/??? /??? .
/?* /?* .
/* /* .
/* /** ../**
/* /*** ../***
/*.* /*.** ../*.**
/*.??? /*.?? ../*.??
/[] /[] .
/[a-z]* /[0-9]* ../[0-9]*
/foo /foo .
/foo / ..
/foo/bar / ../..
/foo/bar /foo ..
/foo/bar /foo/baz ../baz
/foo/bar /bar/foo ../../bar/foo
/foo/bar/baz /gnarf/blurfl/blubb ../../../gnarf/blurfl/blubb
/foo/bar/baz /gnarf ../../../gnarf
/foo/bar/baz /foo/baz ../../baz
/foo. /bar. ../bar.
EOF
while read FROM TO VIA; do
relativepath "$FROM" "$TO"
printf '%s\n' "FROM: $FROM" "TO: $TO" "VIA: $result"
if test "$result" != "$VIA"; then
printf '%s\n' "OOOPS! Expected '$VIA' but got '$result'"
fi
done
# vi: set tabstop=3 shiftwidth=3 expandtab fileformat=unix :
मेरा समाधान:
computeRelativePath()
{
Source=$(readlink -f ${1})
Target=$(readlink -f ${2})
local OLDIFS=$IFS
IFS="/"
local SourceDirectoryArray=($Source)
local TargetDirectoryArray=($Target)
local SourceArrayLength=$(echo ${SourceDirectoryArray[@]} | wc -w)
local TargetArrayLength=$(echo ${TargetDirectoryArray[@]} | wc -w)
local Length
test $SourceArrayLength -gt $TargetArrayLength && Length=$SourceArrayLength || Length=$TargetArrayLength
local Result=""
local AppendToEnd=""
IFS=$OLDIFS
local i
for ((i = 0; i <= $Length + 1 ; i++ ))
do
if [ "${SourceDirectoryArray[$i]}" = "${TargetDirectoryArray[$i]}" ]
then
continue
elif [ "${SourceDirectoryArray[$i]}" != "" ] && [ "${TargetDirectoryArray[$i]}" != "" ]
then
AppendToEnd="${AppendToEnd}${TargetDirectoryArray[${i}]}/"
Result="${Result}../"
elif [ "${SourceDirectoryArray[$i]}" = "" ]
then
Result="${Result}${TargetDirectoryArray[${i}]}/"
else
Result="${Result}../"
fi
done
Result="${Result}${AppendToEnd}"
echo $Result
}
यहाँ मेरा संस्करण है। यह @Offirmo द्वारा उत्तर पर आधारित है । मैंने इसे डैश-संगत बनाया और निम्नलिखित टेस्टकेस विफलता को ठीक किया:
./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/"
कोड देखें:
# both $1 and $2 are absolute paths beginning with /
# returns relative path to $2/$target from $1/$source
CT_FindRelativePath()
{
local insource=$1
local intarget=$2
# Ensure both source and target end with /
# This simplifies the inner loop.
#echo "insource : \"$insource\""
#echo "intarget : \"$intarget\""
case "$insource" in
*/) ;;
*) source="$insource"/ ;;
esac
case "$intarget" in
*/) ;;
*) target="$intarget"/ ;;
esac
#echo "source : \"$source\""
#echo "target : \"$target\""
local common_part=$source # for now
local result=""
#echo "common_part is now : \"$common_part\""
#echo "result is now : \"$result\""
#echo "target#common_part : \"${target#$common_part}\""
while [ "${target#$common_part}" = "${target}" -a "${common_part}" != "//" ]; 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
if [ -z "${result}" ]; then
result="../"
else
result="../$result"
fi
#echo "(w) common_part is now : \"$common_part\""
#echo "(w) result is now : \"$result\""
#echo "(w) target#common_part : \"${target#$common_part}\""
done
#echo "(f) common_part is : \"$common_part\""
if [ "${common_part}" = "//" ]; then
# special case for root (no common path)
common_part="/"
fi
# since we now have identified the common part,
# compute the non-common part
forward_part="${target#$common_part}"
#echo "forward_part = \"$forward_part\""
if [ -n "${result}" -a -n "${forward_part}" ]; then
#echo "(simple concat)"
result="$result$forward_part"
elif [ -n "${forward_part}" ]; then
result="$forward_part"
fi
#echo "result = \"$result\""
# if a / was added to target and result ends in / then remove it now.
if [ "$intarget" != "$target" ]; then
case "$result" in
*/) result=$(echo "$result" | awk '{ string=substr($0, 1, length($0)-1); print string; }' ) ;;
esac
fi
echo $result
return 0
}
लगता है कि यह एक चाल भी करेगा ... (अंतर्निहित परीक्षणों के साथ आता है) :)
ठीक है, कुछ ओवरहेड की उम्मीद है, लेकिन हम यहां बॉर्न शेल कर रहे हैं! ;)
#!/bin/sh
#
# Finding the relative path to a certain file ($2), given the absolute path ($1)
# (available here too http://pastebin.com/tWWqA8aB)
#
relpath () {
local FROM="$1"
local TO="`dirname $2`"
local FILE="`basename $2`"
local DEBUG="$3"
local FROMREL=""
local FROMUP="$FROM"
while [ "$FROMUP" != "/" ]; do
local TOUP="$TO"
local TOREL=""
while [ "$TOUP" != "/" ]; do
[ -z "$DEBUG" ] || echo 1>&2 "$DEBUG$FROMUP =?= $TOUP"
if [ "$FROMUP" = "$TOUP" ]; then
echo "${FROMREL:-.}/$TOREL${TOREL:+/}$FILE"
return 0
fi
TOREL="`basename $TOUP`${TOREL:+/}$TOREL"
TOUP="`dirname $TOUP`"
done
FROMREL="..${FROMREL:+/}$FROMREL"
FROMUP="`dirname $FROMUP`"
done
echo "${FROMREL:-.}${TOREL:+/}$TOREL/$FILE"
return 0
}
relpathshow () {
echo " - target $2"
echo " from $1"
echo " ------"
echo " => `relpath $1 $2 ' '`"
echo ""
}
# If given 2 arguments, do as said...
if [ -n "$2" ]; then
relpath $1 $2
# If only one given, then assume current directory
elif [ -n "$1" ]; then
relpath `pwd` $1
# Otherwise perform a set of built-in tests to confirm the validity of the method! ;)
else
relpathshow /usr/share/emacs22/site-lisp/emacs-goodies-el \
/usr/share/emacs22/site-lisp/emacs-goodies-el/filladapt.el
relpathshow /usr/share/emacs23/site-lisp/emacs-goodies-el \
/usr/share/emacs22/site-lisp/emacs-goodies-el/filladapt.el
relpathshow /usr/bin \
/usr/share/emacs22/site-lisp/emacs-goodies-el/filladapt.el
relpathshow /usr/bin \
/usr/share/emacs22/site-lisp/emacs-goodies-el/filladapt.el
relpathshow /usr/bin/share/emacs22/site-lisp/emacs-goodies-el \
/etc/motd
relpathshow / \
/initrd.img
fi
यह स्क्रिप्ट केवल पथ नामों पर काम करती है। इसे मौजूद होने के लिए किसी भी फाइल की आवश्यकता नहीं है। यदि पारित किए गए मार्ग निरपेक्ष नहीं हैं, तो व्यवहार थोड़ा असामान्य है, लेकिन यह उम्मीद के अनुसार काम करना चाहिए यदि दोनों पथ सापेक्ष हैं।
मैंने इसे केवल OS X पर परीक्षण किया है, इसलिए यह पोर्टेबल नहीं हो सकता है।
#!/bin/bash
set -e
declare SCRIPT_NAME="$(basename $0)"
function usage {
echo "Usage: $SCRIPT_NAME <base path> <target file>"
echo " Outputs <target file> relative to <base path>"
exit 1
}
if [ $# -lt 2 ]; then usage; fi
declare base=$1
declare target=$2
declare -a base_part=()
declare -a target_part=()
#Split path elements & canonicalize
OFS="$IFS"; IFS='/'
bpl=0;
for bp in $base; do
case "$bp" in
".");;
"..") let "bpl=$bpl-1" ;;
*) base_part[${bpl}]="$bp" ; let "bpl=$bpl+1";;
esac
done
tpl=0;
for tp in $target; do
case "$tp" in
".");;
"..") let "tpl=$tpl-1" ;;
*) target_part[${tpl}]="$tp" ; let "tpl=$tpl+1";;
esac
done
IFS="$OFS"
#Count common prefix
common=0
for (( i=0 ; i<$bpl ; i++ )); do
if [ "${base_part[$i]}" = "${target_part[$common]}" ] ; then
let "common=$common+1"
else
break
fi
done
#Compute number of directories up
let "updir=$bpl-$common" || updir=0 #if the expression is zero, 'let' fails
#trivial case (after canonical decomposition)
if [ $updir -eq 0 ]; then
echo .
exit
fi
#Print updirs
for (( i=0 ; i<$updir ; i++ )); do
echo -n ../
done
#Print remaining path
for (( i=$common ; i<$tpl ; i++ )); do
if [ $i -ne $common ]; then
echo -n "/"
fi
if [ "" != "${target_part[$i]}" ] ; then
echo -n "${target_part[$i]}"
fi
done
#One last newline
echo
यह उत्तर प्रश्न के बैश भाग को संबोधित नहीं करता है, लेकिन क्योंकि मैंने इस प्रश्न के उत्तर को Emats में इस कार्यक्षमता को कार्यान्वित करने के लिए उपयोग करने का प्रयास किया है था।
Emacs वास्तव में इस बॉक्स के बाहर के लिए एक समारोह है:
ELISP> (file-relative-name "/a/b/c" "/a/b/c")
"."
ELISP> (file-relative-name "/a/b/c" "/a/b")
"c"
ELISP> (file-relative-name "/a/b/c" "/c/b")
"../../a/b/c"
relpath
फ़ंक्शन) जो अजगर उत्तर दिया है file-relative-name
, वह आपके द्वारा प्रदान किए गए परीक्षण मामलों के लिए समान रूप से व्यवहार करता है ।
यहाँ एक शेल स्क्रिप्ट है जो अन्य कार्यक्रमों को कॉल किए बिना करता है:
#! /bin/env bash
#bash script to find the relative path between two directories
mydir=${0%/}
mydir=${0%/*}
creadlink="$mydir/creadlink"
shopt -s extglob
relpath_ () {
path1=$("$creadlink" "$1")
path2=$("$creadlink" "$2")
orig1=$path1
path1=${path1%/}/
path2=${path2%/}/
while :; do
if test ! "$path1"; then
break
fi
part1=${path2#$path1}
if test "${part1#/}" = "$part1"; then
path1=${path1%/*}
continue
fi
if test "${path2#$path1}" = "$path2"; then
path1=${path1%/*}
continue
fi
break
done
part1=$path1
path1=${orig1#$part1}
depth=${path1//+([^\/])/..}
path1=${path2#$path1}
path1=${depth}${path2#$part1}
path1=${path1##+(\/)}
path1=${path1%/}
if test ! "$path1"; then
path1=.
fi
printf "$path1"
}
relpath_test () {
res=$(relpath_ /path1/to/dir1 /path1/to/dir2 )
expected='../dir2'
test_results "$res" "$expected"
res=$(relpath_ / /path1/to/dir2 )
expected='path1/to/dir2'
test_results "$res" "$expected"
res=$(relpath_ /path1/to/dir2 / )
expected='../../..'
test_results "$res" "$expected"
res=$(relpath_ / / )
expected='.'
test_results "$res" "$expected"
res=$(relpath_ /path/to/dir2/dir3 /path/to/dir1/dir4/dir4a )
expected='../../dir1/dir4/dir4a'
test_results "$res" "$expected"
res=$(relpath_ /path/to/dir1/dir4/dir4a /path/to/dir2/dir3 )
expected='../../../dir2/dir3'
test_results "$res" "$expected"
#res=$(relpath_ . /path/to/dir2/dir3 )
#expected='../../../dir2/dir3'
#test_results "$res" "$expected"
}
test_results () {
if test ! "$1" = "$2"; then
printf 'failed!\nresult:\nX%sX\nexpected:\nX%sX\n\n' "$@"
fi
}
#relpath_test
मुझे कुछ इस तरह की आवश्यकता थी लेकिन जो प्रतीकात्मक लिंक को भी हल करता है। मुझे पता चला कि pwd के पास उस उद्देश्य के लिए एक ध्वज है। मेरी स्क्रिप्ट का एक टुकड़ा संलग्न है। यह शेल स्क्रिप्ट में एक फ़ंक्शन के भीतर है, इसलिए $ 1 और $ 2 है। परिणाम मान, जो START_ABS से END_ABS तक का सापेक्ष पथ है, UPDIRS चर में है। Pwd -P को निष्पादित करने के लिए स्क्रिप्ट प्रत्येक पैरामीटर डायरेक्टरी में है और इसका अर्थ यह भी है कि सापेक्ष पथ पैरामीटर संभाला जाता है। चीयर्स, जिम
SAVE_DIR="$PWD"
cd "$1"
START_ABS=`pwd -P`
cd "$SAVE_DIR"
cd "$2"
END_ABS=`pwd -P`
START_WORK="$START_ABS"
UPDIRS=""
while test -n "${START_WORK}" -a "${END_ABS/#${START_WORK}}" '==' "$END_ABS";
do
START_WORK=`dirname "$START_WORK"`"/"
UPDIRS=${UPDIRS}"../"
done
UPDIRS="$UPDIRS${END_ABS/#${START_WORK}}"
cd "$SAVE_DIR"