Bash का उपयोग करके वर्तमान पथ को दिए गए सापेक्ष पथ में पूर्ण पथ परिवर्तित करें


261

उदाहरण:

absolute="/foo/bar"
current="/foo/baz/foo"

# Magic

relative="../../bar"

मैं जादू कैसे बनाऊंगा (उम्मीद है कि बहुत जटिल कोड नहीं है ...)?


7
उदाहरण के लिए (मेरा मामला अभी) जीसीसी सापेक्ष पथ देने के लिए ताकि यह स्रोत मार्ग परिवर्तन के बावजूद भी उपयोगी डिबग इन्फोस उत्पन्न कर सके।
ऑफमोरो सेप

यू एंड एल: unix.stackexchange.com/questions/100918/… पर इससे संबंधित एक प्रश्न पूछा गया था । जवाब (@Gilles) में से एक के लिए एक उपकरण, का उल्लेख है सिमलिंक , जो इस समस्या का आसान काम कर सकते हैं।
SLM

25
सरल: realpath --relative-to=$absolute $current
केनोरब

जवाबों:


228

GNU कोरुटिल्स 8.23 ​​से रियलपथ का उपयोग करना सबसे सरल है, मुझे लगता है:

$ realpath --relative-to="$file1" "$file2"

उदाहरण के लिए:

$ realpath --relative-to=/usr/bin/nmap /tmp/testing
../../../tmp/testing

7
यह अफ़सोस की बात है कि पैकेज उबंटू 14.04 पर पुराना है और इसमें --relative-to विकल्प नहीं है।
kzh

3
उबंटू 16.04 पर ठीक काम करता है
cayhorstmann

7
$ realpath --relative-to="${PWD}" "$file"यदि आप वर्तमान कार्यशील निर्देशिका के सापेक्ष पथ चाहते हैं तो उपयोगी है।
dcoles

1
यह अंदर बातों के लिए सही है /usr/bin/nmap/-path लेकिन नहीं करने के लिए /usr/bin/nmapसे: nmapकरने के लिए /tmp/testingयह केवल ../../और नहीं 3 बार ../। यह हालांकि काम करता है, क्योंकि ..रूटफुट पर करना है /
पैट्रिक बी।

6
@PatrickB के रूप में। निहित, --relative-to=…एक निर्देशिका की उम्मीद है और जाँच नहीं करता है। इसका मतलब है कि आप एक अतिरिक्त "../" के साथ समाप्त होते हैं यदि आप किसी फ़ाइल के सापेक्ष पथ का अनुरोध करते हैं (जैसा कि यह उदाहरण प्रतीत होता है, क्योंकि /usr/binशायद ही कभी या कभी निर्देशिकाएं नहीं होती हैं और nmapसामान्य रूप से द्विआधारी होती है)
IBBoard

162
$ python -c "import os.path; print os.path.relpath('/foo/bar', '/foo/baz/foo')"

देता है:

../../bar

11
यह काम करता है, और यह विकल्पों को हास्यास्पद लगता है। यह मेरे लिए एक बोनस है xD
हैशवैन

31
+1। ठीक है, आपने धोखा दिया ... लेकिन इसका उपयोग नहीं किया जाना बहुत अच्छा है! relpath(){ python -c "import os.path; print os.path.relpath('$1','${2:-$PWD}')" ; }
MestreLion

4
अफसोस की बात है, यह सार्वभौमिक रूप से उपलब्ध नहीं है: Py.on 2.6 में os.path.relpath नया है।
चेन लेवी

15
@ChenLevy: पायथन 2.6 को 2008 में जारी किया गया था। यह विश्वास करना मुश्किल था कि यह 2012 में सार्वभौमिक रूप से उपलब्ध नहीं था।
MestreLion

11
python -c 'import os, sys; print(os.path.relpath(*sys.argv[1:]))'सबसे स्वाभाविक रूप से और मज़बूती से काम करता है।
मुसीफिल

31

यह @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"

1
Lib offirmo खोल में एकीकृत github.com/Offirmo/offirmo-shell-lib , समारोह «OSL_FILE_find_relative_path» (फ़ाइल «osl_lib_file.sh»)
Offirmo

1
+1। इसे आसानी से किसी भी पथ को संभालने के लिए बनाया जा सकता है (न कि / के साथ शुरू होने वाले पूर्ण मार्ग) / के source=$1; target=$2साथsource=$(realpath $1); target=$(realpath $2)
जोश केली

2
@ जोश वास्तव में, बशर्ते कि डायर वास्तव में मौजूद है ... जो इकाई परीक्षणों के लिए असंबद्ध था;) लेकिन वास्तविक उपयोग में हाँ, realpathकी सिफारिश की जाती है, या source=$(readlink -f $1)आदि यदि
रियलपैथ

मैंने इसे परिभाषित किया $sourceऔर इसे $targetपसंद किया: `अगर [[-ई $ 1]]; तब स्रोत = $ (रीडलिंक -f $ 1); और स्रोत = $ 1; फाई अगर [[-ई $ 2]]; फिर लक्ष्य = $ (रीडलिंक -f $ 2); और लक्ष्य = $ 2; Fi` इस तरह, फ़ंक्शन वास्तविक / मौजूदा पथों के साथ-साथ काल्पनिक निर्देशिकाओं को भी छोटा कर सकता है।
नाथन एस। वाटसन-हाई

1
@ NathanS.Watson-Haigh इससे भी बेहतर, मैंने हाल ही readlinkमें खोजा एक -mविकल्प है जो अभी-अभी करता है;)
ऑफर्मो

26
#!/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/}

अद्भुत लिपि - लघु और स्वच्छ। मैंने एक संपादन लागू किया (सहकर्मी की समीक्षा पर प्रतीक्षा): common_part = $ source / common_part = $ (dirname $ common_part) / गूंज $ {back} $ {लक्ष्य # $ common_part} निर्देशिका नाम के प्रारंभ में अनुचित मिलान के कारण मौजूदा स्क्रिप्ट विफल हो जाएगी तुलना करते समय, उदाहरण के लिए: "/ foo / bar / baz" से "/ foo / barsucks / bonk"। स्लैश को अंतिम संस्करण में और बाहर ले जाना उस बग को सही करता है।
jcwenger

3
यह स्क्रिप्ट बस काम नहीं करती है। एक साधारण "एक निर्देशिका नीचे" परीक्षणों में विफल रहता है। Jcwenger द्वारा किया गया संपादन थोड़ा बेहतर है, लेकिन अतिरिक्त "../" जोड़ना है।
डॉ। व्यक्ति पर्सन II

1
यह मेरे लिए कुछ मामलों में विफल रहता है यदि एक अनुगामी "/" तर्क पर है; उदाहरण के लिए, यदि $ 1 = "$ HOME /" और $ 2 = "$ HOME / अस्थायी", तो यह "/ घर / उपयोगकर्ता / अस्थायी /" लौटता है, लेकिन यदि $ 1 = $ HOME तो यह सापेक्ष पथ "temp" को ठीक से लौटाता है। दोनों स्रोत = $ 1 और लक्ष्य = $ 2 इसलिए sed (या बैश वैरिएबल प्रतिस्थापन का उपयोग करके "साफ़ किया जा सकता है, लेकिन यह अनावश्यक रूप से अपारदर्शी हो सकता है) जैसे कि>> स्रोत = $ (गूंज" $ {1} "। Sed / s /) \ / * $ // ')
माइकल

1
मामूली सुधार: स्रोत / लक्ष्य को सीधे $ 1 और $ 2 पर सेट करने के बजाय, करें: source = $ (cd $ 1; pwd) लक्ष्य = $ (cd $ 2; pwd)। इस तरह से यह पथ संभालती है। और .. सही ढंग से।
जोसेफ गार्विन

4
शीर्ष-मतदान उत्तर होने के बावजूद, इस उत्तर में बहुत सी सीमाएँ हैं, इसलिए बहुत सारे उत्तर पोस्ट किए जा रहे हैं। इसके बजाय अन्य उत्तर देखें, विशेष रूप से एक प्रदर्शित परीक्षण मामले। और कृपया इस टिप्पणी को लाइक करें!
ऑफिरमो

25

यह 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

... ठीक काम करेगा।


3
sayलॉग के रूप में पर्ल में उपलब्ध नहीं है, लेकिन इसका उपयोग यहां प्रभावी रूप से किया जा सकता है। perl -MFile::Spec -E 'say File::Spec->abs2rel(@ARGV)'
विलियम पर्ससेल

+1 लेकिन इस तरह के उत्तर को भी देखें जो कि पुराना है (फरवरी 2012)। विलियम पर्सेल की प्रासंगिक टिप्पणियाँ भी पढ़ें । मेरा संस्करण दो कमांड लाइन हैं: perl -MFile::Spec -e 'print File::Spec->abs2rel(@ARGV)' "$target"और perl -MFile::Spec -e 'print File::Spec->abs2rel(@ARGV)' "$target" "$origin"। पहली एक-लाइन पर्ल स्क्रिप्ट एक तर्क का उपयोग करती है (मूल वर्तमान कार्यशील निर्देशिका है)। दूसरी एक-लाइन पर्ल स्क्रिप्ट दो तर्कों का उपयोग करती है।
ओलिब्रे

3
यही स्वीकृत उत्तर होना चाहिए। perlलगभग हर जगह पाया जा सकता है, हालांकि जवाब अभी भी एक-लाइनर है।
दिमित्री गिन्ज़बर्ग

19

यह मानते हुए कि आपने स्थापित किया है: बैश, पीडब्ल्यूडी, डिरनेम, इको; फिर रिलेपाथ है

#!/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 और कुछ अन्य विचारों

नोट : इसके लिए मौजूदा फ़ोल्डर के लिए दोनों रास्तों की आवश्यकता होती है। फाइलें काम नहीं करेंगी।


2
आदर्श उत्तर: / बिन / श के साथ काम करता है, इसके लिए रीडलिंक, अजगर, पर्ल की आवश्यकता नहीं होती है -> प्रकाश / एम्बेडेड सिस्टम या विंडोज़ बैश कंसोल के लिए महान
फ्रेंकोइस

2
दुर्भाग्य से, इसके लिए आवश्यक है कि पथ मौजूद है, जो हमेशा वांछित नहीं होता है।
drwatsoncode

ईश्वरीय उत्तर। Cd-pwd सामान मेरे द्वारा अनुमानित लिंक को हल करने के लिए है? अच्छा गोल्फ!
टेक-फ्रीक

15

पायथन os.path.relpathएक शेल फ़ंक्शन के रूप में

इस relpathअभ्यास का लक्ष्य पायथन 2.7 के os.path.relpathकार्य की नकल करना है (पायथन संस्करण 2.6 से उपलब्ध है, लेकिन केवल 2.7 में ठीक से काम कर रहा है), जैसा कि xni द्वारा प्रस्तावित है । परिणामस्वरूप, कुछ परिणाम अन्य उत्तरों में दिए गए कार्यों से भिन्न हो सकते हैं।

(मैंने रास्तों में newlines के साथ परीक्षण नहीं किया है क्योंकि यह python -cZSH से कॉल करने के आधार पर मान्यता को तोड़ता है । यह निश्चित रूप से कुछ प्रयासों के साथ संभव होगा।)

बाश में "जादू" के बारे में, मैंने बहुत पहले बाश में जादू की तलाश छोड़ दी है, लेकिन मैंने तब से सभी जादू की जरूरत है, और फिर कुछ, 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 शेल फ़ंक्शन

सबसे पहले, 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 खोल समारोह

अब, अधिक मजबूत 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

2
जब पहला रास्ता समाप्त होता है तो /मुझे डर नहीं लगता।
नोल्डोरिन

12
#!/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")"; }

शेल स्क्रिप्ट के ऊपर पिनी के द्वारा प्रेरित किया गया था (धन्यवाद!)। यह स्टैक ओवरफ्लो के कम से कम हाइलाइटिंग मॉड्यूल में एक बग को ट्रिगर करता है (कम से कम मेरे पूर्वावलोकन फ्रेम में)। इसलिए कृपया ध्यान दें कि यदि हाइलाइटिंग गलत है।

कुछ नोट:

  • हटाए गए त्रुटियों और बेहतर कोड को बिना कोड की लंबाई और जटिलता को बढ़ाए
  • उपयोग में आसानी के लिए कार्यों में कार्यक्षमता डालें
  • सभी POSIX गोले (डैश, बैश और उबंटू लिनक्स 12.04 में zsh के साथ परीक्षण) के साथ काम करते हैं ताकि वे (चाहिए) POSIX संगत कार्य करें।
  • ग्लोबल वेरिएबल्स को क्लोब करने और वैश्विक नाम स्थान को प्रदूषित करने से बचने के लिए केवल स्थानीय चर का उपयोग किया
  • दोनों निर्देशिका पथ मौजूद नहीं हैं (मेरे आवेदन के लिए आवश्यकता)
  • पथनाम में रिक्त स्थान, विशेष वर्ण, नियंत्रण वर्ण, बैकस्लैश, टैब, ', ",?, *, [,], आदि हो सकते हैं।
  • कोर फ़ंक्शन "relPath" केवल POSIX शेल बिल्डरों का उपयोग करता है, लेकिन मापदंडों के रूप में विहित पूर्ण निर्देशिका पथ की आवश्यकता होती है
  • विस्तारित फ़ंक्शन "रिलेपाथ" मनमाने ढंग से निर्देशिका पथ (सापेक्ष, गैर-विहित) भी संभाल सकता है, लेकिन बाहरी GNU कोर उपयोगिता "रीडलिंक" की आवश्यकता होती है
  • दो कारणों के बजाय बिलियन "इको" और बिल्ट "प्रिंटफ" का उपयोग करने से बचें:
  • अनावश्यक रूपांतरणों से बचने के लिए, पाथनेम्स का उपयोग किया जाता है क्योंकि वे शेल और ओएस उपयोगिताओं (जैसे सीडी, एलएन, एलएस, मिल, एमकेडीआर द्वारा अपेक्षित हैं); अजगर के "os.path.relpath के विपरीत; जो कुछ बैकस्लैश अनुक्रमों की व्याख्या करेगा"
  • उल्लिखित बैकस्लैश अनुक्रमों को छोड़कर, फ़ंक्शन के अंतिम पंक्ति "relPath" आउटपुट पैथेमोन के संगत पैथेमोन:

    path=$up${path#"$common"/}; path=${path%/}; printf %s "${path:-.}"

    अंतिम पंक्ति को लाइन द्वारा प्रतिस्थापित (और सरलीकृत) किया जा सकता है

    printf %s "$up${path#"$common"/}"

    मैं बाद पसंद करते हैं क्योंकि

    1. फ़ाइल नाम सीधे relpath द्वारा प्राप्त dir पथों के लिए जोड़ा जा सकता है, उदाहरण के लिए:

      ln -s "$(relpath "<fromDir>" "<toDir>")<file>" "<fromDir>"
    2. इस विधि के साथ बनाए गए एक ही डायर में प्रतीकात्मक लिंक में "./"फ़ाइल नाम का बदसूरत हिस्सा नहीं होता है ।

  • यदि आपको कोई त्रुटि मिलती है तो कृपया linuxball (at) gmail.com पर संपर्क करें और मैं इसे ठीक करने का प्रयास करूंगा।
  • जोड़ा प्रतिगमन परीक्षण सूट (भी POSIX खोल संगत)

प्रतिगमन परीक्षणों के लिए कोड सूची (केवल इसे शेल स्क्रिप्ट में जोड़ें):

############################################################################
# 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

9

यहां बहुत सारे उत्तर हर दिन उपयोग के लिए व्यावहारिक नहीं हैं। चूंकि शुद्ध बैश में इसे ठीक से करना बहुत मुश्किल है, इसलिए मैं निम्नलिखित, विश्वसनीय समाधान सुझाता हूं (एक टिप्पणी में दफन एक सुझाव के समान):

function relpath() { 
  python -c "import os,sys;print(os.path.relpath(*(sys.argv[1:])))" "$@";
}

फिर, आप वर्तमान निर्देशिका के आधार पर सापेक्ष पथ प्राप्त कर सकते हैं:

echo $(relpath somepath)

या आप यह निर्दिष्ट कर सकते हैं कि पथ किसी दिए गए निर्देशिका के सापेक्ष है:

echo $(relpath somepath /etc)  # relative to /etc

एक नुकसान यह है कि अजगर की आवश्यकता है, लेकिन:

  • यह किसी भी अजगर> = 2.6 में पहचान के अनुसार काम करता है
  • यह आवश्यक नहीं है कि फाइलें या निर्देशिका मौजूद हों।
  • फ़ाइल नाम में विशेष वर्णों की एक विस्तृत श्रृंखला हो सकती है। उदाहरण के लिए, कई अन्य समाधान काम नहीं करते हैं यदि फ़ाइलनाम में रिक्त स्थान या अन्य विशेष वर्ण होते हैं।
  • यह एक-लाइन फ़ंक्शन है जो स्क्रिप्ट को अव्यवस्थित नहीं करता है।

ध्यान दें कि जो समाधान शामिल हैं basenameया dirnameजरूरी नहीं कि वे बेहतर हों, क्योंकि उन्हें coreutilsस्थापित करने की आवश्यकता होती है । यदि किसी के पास एक शुद्ध bashसमाधान है जो विश्वसनीय और सरल है (एक दृढ़ जिज्ञासा के बजाय), तो मुझे आश्चर्य होगा।


यह अब तक का सबसे मजबूत दृष्टिकोण है।
dimo414

7

यह स्क्रिप्ट केवल उन इनपुट के लिए सही परिणाम देती है जो बिना .या उसके पूर्ण पथ या सापेक्ष पथ हैं ..:

#!/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"

1
यह काम करने के लिए प्रकट होता है। यदि निर्देशिका वास्तव में मौजूद है, तो इनपुट पर $ (readlink -f $ 1) और $ (readlink -f $ 2) का उपयोग समस्या को ठीक कर सकता है जहां "।" या ".." इनपुट्स में दिखाई देता है। यदि निर्देशिका वास्तव में मौजूद नहीं है तो यह कुछ परेशानी पैदा कर सकता है।
डॉ। पर्सन II II

7

मैं सिर्फ इस गैर-तुच्छ कार्य के लिए पर्ल का उपयोग करूंगा:

absolute="/foo/bar"
current="/foo/baz/foo"

# Perl is magic
relative=$(perl -MFile::Spec -e 'print File::Spec->abs2rel("'$absolute'","'$current'")')

1
+1, लेकिन अनुशंसा करेगा: perl -MFile::Spec -e "print File::Spec->abs2rel('$absolute','$current')"ताकि निरपेक्ष और वर्तमान उद्धृत हो।
विलियम पर्ससेल

मुझे पसंद है relative=$(perl -MFile::Spec -e 'print File::Spec->abs2rel(@ARGV)' "$absolute" "$current")। यह सुनिश्चित करता है कि मान स्वयं नहीं हो सकते, जिसमें पर्ल कोड हो!
एरिक एरोनेस्टी

6

कास्कू और पिनी के उत्तरों पर एक मामूली सुधार , जो रिक्त स्थान के साथ अच्छा खेलता है और रिश्तेदार पथों को पारित करने की अनुमति देता है:

#!/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

4

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

कॉम्पैक्टनेस और बैश-नेस के लिए +1। हालांकि, आप भी बुलाना चाहिए readlinkपर $(pwd)
DevSolar

2
रिलेटिव का मतलब फाइल को उसी डायरेक्टरी में रखना नहीं है।
ग्रीनल्डमैन

हालांकि मूल प्रश्न बहुत सारे टेस्टकेस प्रदान नहीं करता है, लेकिन यह स्क्रिप्ट सरल परीक्षणों के लिए विफल है जैसे रिश्तेदार घर / user1 से / home / user2 (सही उत्तर: ../user2) को खोजने के लिए। पीनी / jcwenger द्वारा स्क्रिप्ट इस मामले के लिए काम करती है।
माइकल

4

फिर भी एक और समाधान, शुद्ध 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 पर वापस जाएं। धन्यवाद।


3

अफसोस की बात है कि मार्क रुशकॉफ़ का जवाब (अब हटा दिया गया है) ने इसे कोड से संदर्भित किया यहां है ) सही तरीके से काम नहीं करता है:

source=/home/part2/part3/part4
target=/work/proj1/proj2

कमेंट्री में बताई गई सोच को ज्यादातर मामलों के लिए सही ढंग से काम करने के लिए परिष्कृत किया जा सकता है। मैं यह मानने वाला हूं कि स्क्रिप्ट एक स्रोत तर्क (जहां आप हैं) और एक लक्ष्य तर्क (जहां आप प्राप्त करना चाहते हैं) को लेती है, और यह कि दोनों पूर्ण पथ प्रदर्शक हैं या दोनों सापेक्ष हैं। यदि कोई निरपेक्ष है और दूसरा सापेक्ष है, तो सबसे आसान काम है कि वर्तमान कार्य निर्देशिका के सापेक्ष नाम को उपसर्ग करना - लेकिन नीचे दिया गया कोड ऐसा नहीं करता है।


खबरदार

नीचे दिया गया कोड सही ढंग से काम करने के करीब है, लेकिन काफी सही नहीं है।

  1. डेनिस विलियमसन की टिप्पणियों में संबोधित समस्या है।
  2. एक समस्या यह भी है कि इस विशुद्ध रूप से पाठ मार्ग के प्रसंस्करण और आपको गंभीर रूप से अजीब सहानुभूति द्वारा गड़बड़ किया जा सकता है।
  3. कोड पथ में आवारा 'डॉट्स' को हैंडल नहीं करता है जैसे 'xyz/./pqr ' ।
  4. कोड पथ में आवारा 'डबल डॉट्स' को संभालता नहीं है जैसे 'xyz/../pqr ' ।
  5. तुच्छ रूप से: कोड ./पथ से प्रमुख ' ' को नहीं हटाता है।

डेनिस का कोड बेहतर है क्योंकि यह 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करने के लिए /एक बहुत अधिक है ../। अन्यथा, मेरी स्क्रिप्ट आपके आउटपुट से मेल खाती है, मेरे अलावा .जहां गंतव्य है, उसके अंत में एक अनावश्यक जोड़ता है .और मैं उन ./लोगों की शुरुआत में उपयोग नहीं करता हूं जो ऊपर जाने के बिना उतरते हैं।
अगली सूचना तक रोक दिया गया।

@ डेनिस: मैंने परिणामों पर क्रॉस-आईडेड होने में समय बिताया - कभी-कभी मैं उस समस्या को देख सकता था, और कभी-कभी मैं इसे फिर से नहीं पाता। एक प्रमुख './' हटाना एक और तुच्छ कदम है। आपकी टिप्पणी 'कोई एम्बेड नहीं है। या .. 'भी प्रासंगिक है। वास्तव में ठीक से काम करना आश्चर्यजनक रूप से कठिन है - दोगुना इसलिए यदि कोई भी नाम वास्तव में एक सिमलिंक है; हम दोनों विशुद्ध रूप से शाब्दिक विश्लेषण कर रहे हैं।
जोनाथन लेफ़लर

@ डेनिस: निश्चित रूप से, जब तक कि आपके पास न्यूकैसल कनेक्शन नेटवर्किंग नहीं है, जड़ से ऊपर जाने की कोशिश करना व्यर्थ है, इसलिए ../../../ .. .. और ../// .. बराबर हैं। हालाँकि, यह शुद्ध पलायनवाद है; आपकी आलोचना सही है। (न्यूकैसल कनेक्शन ने आपको एक अलग होस्ट - नीट स्कीम को प्राप्त करने के लिए संकेतन /../host/path/on/remote/machine को कॉन्फ़िगर करने और उपयोग करने की अनुमति दी। मेरा मानना ​​है कि यह समर्थित है /../../network/host/। पाथ / ऑन / रिमोट / नेटवर्क / और / होस्ट भी। यह विकिपीडिया पर है।)
जोनाथन लेफ़लर

इसके बजाय, अब हमारे पास UNC का दोहरा स्लैश है।
अगली सूचना तक रोक दिया गया।

1
यदि आप इसे "-f" विकल्प पास करते हैं तो "रीडलिंक" उपयोगिता (कम से कम GNU संस्करण) realpath () के बराबर कर सकती है। उदाहरण के लिए, मेरे सिस्टम पर, readlink /usr/bin/viदेता है /etc/alternatives/vi, लेकिन वह एक और सिम्कलिन है - जबकि readlink -f /usr/bin/viदेता है /usr/bin/vim.basic, जो सभी
सिम्बलिंक्स

3

मैंने आपके प्रश्न को "पोर्टेबल" शेल कोड में लिखने की चुनौती के रूप में लिया, अर्थात

  • मन में एक POSIX खोल के साथ
  • कोई बशर्ते जैसे कि सरणियाँ नहीं
  • प्लेग की तरह बाह्य फोन करने से बचें। स्क्रिप्ट में एक भी कांटा नहीं है! यह विशेष रूप से साइबर फोर्क जैसे महत्वपूर्ण कांटा उपरि के साथ सिस्टम पर धधकते हुए तेज होता है।
  • Pathnames (*;;, [,]) में ग्लोब पात्रों से निपटना चाहिए

यह किसी भी 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 :

2

मेरा समाधान:

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

}

यह एक्सट्रीमली पोर्टेबल है
बेनामी

2

यहाँ मेरा संस्करण है। यह @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
}

1

लगता है कि यह एक चाल भी करेगा ... (अंतर्निहित परीक्षणों के साथ आता है) :)

ठीक है, कुछ ओवरहेड की उम्मीद है, लेकिन हम यहां बॉर्न शेल कर रहे हैं! ;)

#!/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

1

यह स्क्रिप्ट केवल पथ नामों पर काम करती है। इसे मौजूद होने के लिए किसी भी फाइल की आवश्यकता नहीं है। यदि पारित किए गए मार्ग निरपेक्ष नहीं हैं, तो व्यवहार थोड़ा असामान्य है, लेकिन यह उम्मीद के अनुसार काम करना चाहिए यदि दोनों पथ सापेक्ष हैं।

मैंने इसे केवल 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

इसके अलावा, कोड थोड़ा कॉपी और पेस्ट-ईश है, लेकिन मुझे इसके बजाय जल्दी चाहिए था।
किशोर डे 6'10

अच्छा ... बस मुझे जो चाहिए था। और आपने एक कैनोनिकलाइजिंग रूटीन को शामिल किया है जो कि अन्य लोगों की तुलना में बेहतर है जो मैंने देखा है (जो आमतौर पर रेगेक्स प्रतिस्थापन पर भरोसा करते हैं)।
drwatsoncode

0

यह उत्तर प्रश्न के बैश भाग को संबोधित नहीं करता है, लेकिन क्योंकि मैंने इस प्रश्न के उत्तर को 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, वह आपके द्वारा प्रदान किए गए परीक्षण मामलों के लिए समान रूप से व्यवहार करता है ।
गैरी विस्न्यूस्की

-1

यहाँ एक शेल स्क्रिप्ट है जो अन्य कार्यक्रमों को कॉल किए बिना करता है:

#! /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

स्रोत: http://www.ynform.org/w/Pub/Relpath


1
$ {परम / पैटर्न / विकल्प} निर्माण के उपयोग के कारण यह वास्तव में पोर्टेबल नहीं है, जो कि POSIX (2011 के अनुसार) नहीं है।
जेन्स

संदर्भित स्रोत ynform.org/w/Pub/Relpath कई बार स्क्रिप्ट सामग्री वाले पूरी तरह से विकृत विकी पृष्ठ पर इंगित करता है, vi tilde लाइनों के साथ परस्पर क्रिया करता है , कमांड के बारे में त्रुटि संदेश नहीं मिला और क्या नहीं। मूल शोध करने वाले किसी व्यक्ति के लिए बिलकुल बेकार।
जेन्स

-1

मुझे कुछ इस तरह की आवश्यकता थी लेकिन जो प्रतीकात्मक लिंक को भी हल करता है। मुझे पता चला कि 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"
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.