বাশ ব্যবহার করে একটি বর্তমান ডিরেক্টরি প্রদত্ত পরম পাথকে আপেক্ষিক পথে রূপান্তর করুন


261

উদাহরণ:

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

# Magic

relative="../../bar"

আমি কীভাবে যাদু তৈরি করব (আশা করি খুব জটিল কোড নয় ...)?


7
উদাহরণস্বরূপ (এখনই আমার কেস) জিসিসি আপেক্ষিক পাথ দেওয়ার জন্য যাতে উত্সের পথ পরিবর্তন হয় এমনকি আপেক্ষিক ডিবাগ ইনফস ব্যবহারযোগ্য gene
অফির্মো

এর অনুরূপ একটি প্রশ্ন U&L এ জিজ্ঞাসা করা হয়েছিল: unix.stackexchange.com/questions/100918/… । উত্তর (@Gilles) একজন টুল, উল্লেখ symlinks , যা এই সমস্যার সহজ কাজ করতে পারেন।
slm

25
সরল: realpath --relative-to=$absolute $current
কেনারব

উত্তর:


228

জিএনইউ কোর্টিলস ৮.২৩ থেকে রিয়েলপথ ব্যবহার করা সবচেয়ে সহজ, আমি মনে করি:

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

উদাহরণ স্বরূপ:

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

7
এটি অত্যন্ত দুঃখের বিষয় যে প্যাকেজটি উবুন্টু 14.04 এ পুরানো এবং এতে - সম্পর্কিত সম্পর্কিত বিকল্প নেই।
kzh

3
উবুন্টু 16.04
cayhorstmann

7
$ realpath --relative-to="${PWD}" "$file"আপনি যদি বর্তমান ওয়ার্কিং ডিরেক্টরিটির সাথে সম্পর্কিত পাথগুলি চান তা কার্যকর।
dcoles

1
এই ভিতরে জিনিসের জন্য সঠিক /usr/bin/nmap/-path কিন্তু জন্য /usr/bin/nmapথেকে: nmapথেকে /tmp/testingএটা শুধুমাত্র ../../এবং 3 বার ../। এটি তবে কাজ করে, কারণ ..rootfs এ করা /
প্যাট্রিক বি।

6
হিসাবে @ পেট্রিকবি। অন্তর্নিহিত, --relative-to=…একটি ডিরেক্টরি আশা করে এবং চেক করে না। এর অর্থ হ'ল আপনি অতিরিক্ত "../" দিয়ে শেষ করেছেন যদি আপনি কোনও ফাইলের সাথে সম্পর্কিত কোনও পাথের জন্য অনুরোধ করেন (উদাহরণ হিসাবে এটি প্রদর্শিত হয়, কারণ /usr/binখুব কমই বা কখনই ডিরেক্টরি থাকে না এবং nmapসাধারণত একটি বাইনারি হয়)
আইবিবোর্ড

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

দেয়:

../../bar

11
এটি কাজ করে এবং এটি বিকল্পগুলি হাস্যকর দেখায়। এটি আমার জন্য এক্সডি বোনাস
v

31
+1 টি। ঠিক আছে, আপনি প্রতারণা করেছেন ... তবে এটি ব্যবহার করা ভাল না! relpath(){ python -c "import os.path; print os.path.relpath('$1','${2:-$PWD}')" ; }
MestreLion

4
দুঃখজনকভাবে, এটি সর্বজনীনভাবে উপলভ্য নয়: os.path.relpath পাইথন ২.6 এ নতুন।
চেন লেভি

15
@ChenLevy: পাইথন 2.6 মধ্যে 2008. হার্ড মুক্তি বিশ্বাস করতে তা বিশ্বজুড়ে উপলব্ধ 2012 ছিল না
MestreLion

11
python -c 'import os, sys; print(os.path.relpath(*sys.argv[1:]))'সবচেয়ে প্রাকৃতিক এবং নির্ভরযোগ্যভাবে কাজ করে।
মুশিফিল

31

এটি @ পিনি থেকে বর্তমানে সর্বাধিক নির্ধারিত সমাধানটির একটি সংশোধনযোগ্য, সম্পূর্ণ ক্রিয়ামূলক উন্নতি (যা দুঃখের সাথে কেবল কয়েকটি ক্ষেত্রে পরিচালিত হয়)

অনুস্মারক: স্ট্রিং শূন্য দৈর্ঘ্য (= খালি) হলে '-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
Liberal এর সংক্ষিপ্ত রূপ 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মতো: `যদি [[-e $ 1]]; তারপরে উত্স = $ (রিডলিঙ্ক -f f 1); অন্য উত্স = $ 1; ফাই যদি [[-e $ 2]]; তারপরে টার্গেট = read (রিডলিঙ্ক -f $ 2); অন্য লক্ষ্য = $ 2; fi` এইভাবে, ফাংশনটি সত্যিকারের / বিদ্যমান আপেক্ষিক পাথের পাশাপাশি কাল্পনিক ডিরেক্টরিগুলিকে হ্যান্ডেল করতে পারে।
নাথান এস ওয়াটসন-হাই হাই

1
@ নাথানস.ওয়াটসন-হাই হাই আরও ভাল, আমি সম্প্রতি আবিষ্কার করেছি 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/}

বিস্ময়কর স্ক্রিপ্ট - সংক্ষিপ্ত এবং পরিষ্কার। আমি একটি সম্পাদনা প্রয়োগ করেছি (পিয়ার পর্যালোচনার অপেক্ষায়): কমন_ পার্ট = $ সোর্স / কমন_ পার্ট = $ (ডায়ারনাম $ কমন_ পার্ট) / ইকো $ {ব্যাক} {লক্ষ্য # $ কমন_ পার্ট art বিদ্যমান স্ক্রিপ্ট ডিরেক্টরি নামের শুরুতে অনুপযুক্ত ম্যাচের কারণে ব্যর্থ হবে তুলনা করার সময়, উদাহরণস্বরূপ: "/ foo / বার / বাজ" থেকে "/ foo / বারসকস / বনক"। স্ল্যাশটি ভারে এবং চূড়ান্ত রূপের বাইরে নিয়ে যাওয়া সেই বাগটিকে সংশোধন করে।
jcwenger

3
এই স্ক্রিপ্টটি সহজভাবে কাজ করে না। একটি সাধারণ "একটি ডিরেক্টরি ডাউন" পরীক্ষায় ব্যর্থ হয়। Jcwenger এর সম্পাদনাগুলি কিছুটা ভাল কাজ করে তবে একটি অতিরিক্ত "../" যোগ করার ঝোঁক।
ডাঃ ব্যক্তি ব্যক্তি ২ য়

1
এটি কিছু ক্ষেত্রে আমার পক্ষে ব্যর্থ হয় যদি "/" যুক্তিতে থাকে; উদাহরণস্বরূপ, যদি $ 1 = "$ হোম / /" এবং $ 2 = "$ হোম / টেম্প" হয় তবে এটি "/ হোম / ইউজার / টেম্প /" ফিরিয়ে দেয় তবে যদি $ 1 = $ হোম থাকে তবে এটি সঠিকভাবে আপেক্ষিক পাথ "টেম্প" ফিরিয়ে দেয়। উভয় উত্স = $ 1 এবং লক্ষ্য = $ 2 সুতরাং সেড (বা ব্যাশ ভেরিয়েবল বিকল্প ব্যবহার করে, তবে এটি অকারণেই অস্বচ্ছ হতে পারে) যেমন => উত্স = $ (প্রতিধ্বনি "$ {1}" | সেড / এর / \ / * $ // ')
মাইকেল

1
গৌণ উন্নতি: সরাসরি source 1 এবং $ 2 এ উত্স / লক্ষ্য নির্ধারণের পরিবর্তে করুন: উত্স = $ (সিডি $ 1; পিডাব্লুডি) লক্ষ্য = $ (সিডি $ 2; পিডাব্লুডি) d এইভাবে এটি দিয়ে পাথ পরিচালনা করে। এবং .. সঠিকভাবে।
জোসেফ গারভিন

4
শীর্ষ-ভোট প্রাপ্ত উত্তর হওয়া সত্ত্বেও, এই উত্তরটির অনেক সীমাবদ্ধতা রয়েছে, তাই অন্য অনেক উত্তর পোস্ট করা হচ্ছে। পরিবর্তে অন্যান্য উত্তরগুলি দেখুন, বিশেষত পরীক্ষার কেসগুলি প্রদর্শন করে। এবং দয়া করে এই মন্তব্য upvote!
অফিরমো

25

এটি ২০০১ সাল থেকে পারলে অন্তর্নির্মিত , তাই এটি আপনি কল্পনা করতে পারেন এমন প্রায় প্রতিটি সিস্টেমে এমনকি ভিএমএসেও কাজ করে

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 তবে এই একই উত্তরটিও দেখুন যা পুরানো (ফেব্রুয়ারী ২০১২)। উইলিয়াম পার্সেলের প্রাসঙ্গিক মন্তব্যগুলিও পড়ুন । আমার সংস্করণ দুটি কমান্ড লাইন: perl -MFile::Spec -e 'print File::Spec->abs2rel(@ARGV)' "$target"এবং perl -MFile::Spec -e 'print File::Spec->abs2rel(@ARGV)' "$target" "$origin"। প্রথম এক-লাইন পার্ল স্ক্রিপ্টটিতে একটি আর্গুমেন্ট ব্যবহার করা হয় (মূলটি বর্তমান কার্যক্ষম ডিরেক্টরি)। দ্বিতীয় এক-লাইনের পার্ল স্ক্রিপ্ট দুটি আর্গুমেন্ট ব্যবহার করে।
অলিবরে

3
এটি গ্রহণযোগ্য উত্তর হওয়া উচিত। perlউত্তরটি এখনও এক-রেখাযুক্ত হলেও প্রায় সর্বত্রই পাওয়া যাবে।
দিমিত্রি জিনজবার্গ

19

ধরে নেই যে আপনি ইনস্টল করেছেন: বাশ, পিডব্লিউডি, ডারনাম, ইকো; তাহলে রেলপথ হয় is

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

আমি পিনি এবং অন্যান্য কয়েকটি ধারণার উত্তর গল্ফ করেছি

দ্রষ্টব্য : বিদ্যমান ফোল্ডারগুলির জন্য এটি উভয় পাথের প্রয়োজন। ফাইলগুলি কাজ করবে না


2
আদর্শ উত্তর: সঙ্গে / বিন / SH কাজ, না প্রয়োজন readlink, পাইথন, পার্ল নেই -> আলো / এমবেডেড সিস্টেম অথবা উইন্ডো জন্য মহান কনসোল bash
ফ্রাঁসোয়া

2
দুর্ভাগ্যক্রমে, এর জন্য প্রয়োজন যে পথটি বিদ্যমান, যা সর্বদা পছন্দসই নয়।
ড্রওয়াটসনকোড

Lyশ্বরিক উত্তর। সিডি-পিডাব্লুডি স্টাফগুলি আমার ধারণা লিংকগুলি সমাধান করার জন্য? দুর্দান্ত গল্ফিং!
টেক-ফ্রিক

15

পাইথন os.path.relpathশেল ফাংশন হিসাবে

এই relpathঅনুশীলনের লক্ষ্যটি পাইথন ২.7 এর os.path.relpathক্রিয়াকলাপটি নকল করা (পাইথন সংস্করণ ২.6 থেকে পাওয়া তবে কেবল ২. 2.-তে সঠিকভাবে কাজ করা), এক্সনি দ্বারা প্রস্তাবিত হিসাবে । ফলস্বরূপ, কিছু ফলাফল অন্যান্য উত্তরে প্রদত্ত ফাংশন থেকে পৃথক হতে পারে।

(আমি python -cজাস্টএসএইচ থেকে কল করার উপর ভিত্তি করে বৈধতা ভঙ্গ করেছি বলে পাথগুলিতে নতুন লাইনের সাথে পরীক্ষা করিনি । কিছু প্রচেষ্টা দিয়ে অবশ্যই এটি সম্ভব হবে))

বাশ-এ "যাদু" সম্পর্কে, আমি অনেক আগে বাশে ম্যাজিক সন্ধান করা ছেড়ে দিয়েছি, তবে আমি তখন থেকে আমার প্রয়োজনীয় সমস্ত যাদু এবং পরে কিছু জেডএসএইচে খুঁজে পেয়েছি in

ফলস্বরূপ, আমি দুটি বাস্তবায়ন প্রস্তাব।

প্রথম প্রয়োগটি সম্পূর্ণরূপে পসিক্স-সম্মতিযুক্ত হওয়ার লক্ষ্য । আমি এটি /bin/dashডেবিয়ান 6.0.6 "চেপে ধরুন" দিয়ে পরীক্ষা করেছি । এটি /bin/shওএস এক্স ১০.৮.৩ এ পুরোপুরি কাজ করে যা আসলে পসিক্স শেল বলে ভান করে বাশ সংস্করণ ৩.২।

দ্বিতীয় বাস্তবায়নটি একটি জেডএসএইচ শেল ফাংশন যা একাধিক স্ল্যাশ এবং পাথের অন্যান্য উপদ্রবগুলির বিরুদ্ধে শক্ত। যদি আপনার কাছে জেডএসএইচ উপলব্ধ থাকে তবে এটি প্রস্তাবিত সংস্করণ, এমনকি যদি আপনি #!/usr/bin/env zshঅন্য শেল থেকে নীচে উপস্থাপিত (যেমন একটি শেবাং দিয়ে ) উপস্থাপিত স্ক্রিপ্ট আকারে কল করে থাকেন ।

অবশেষে, আমি একটি জেডএসএইচ স্ক্রিপ্ট লিখেছি যা অন্যান্য উত্তরে প্রদত্ত পরীক্ষার ক্ষেত্রে প্রদত্ত relpathকমান্ডের আউটপুট যাচাই করে $PATH। আমি এই পরীক্ষাগুলিতে কিছু স্পেস, ট্যাব এবং বিরামচিহ্ন যুক্ত করে যেমন ! ? *এখানে এবং সেখানে যুক্ত করে কিছু মশলা যুক্ত করেছি এবং ভিআইএম-পাওয়ারলাইনে পাওয়া বিদেশী ইউটিএফ -8 অক্ষরের সাথে আরও একটি পরীক্ষায় ফেলেছি

পসিক্স শেল ফাংশন

প্রথমত, পসিক্স-কমপ্লায়েন্ট শেল ফাংশন। এটি বিভিন্ন পাথের সাথে কাজ করে তবে একাধিক স্ল্যাশ পরিষ্কার করে না বা সিমলিংকগুলি সমাধান করে না।

#!/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প্যাকেজে উপলব্ধ) আর্গুমেন্টগুলি সমাধান করতে চান , তবে :a3 এবং 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")"; }

শেল স্ক্রিপ্টের উপরে পিনির (ধন্যবাদ!) দ্বারা অনুপ্রাণিত হয়েছিল । এটি স্ট্যাক ওভারফ্লো (কমপক্ষে আমার পূর্বরূপ ফ্রেমে) সিনট্যাক্স হাইলাইটিং মডিউলে একটি বাগ ট্রিগার করে। তাই হাইলাইটিং ভুল হলে দয়া করে এড়িয়ে যান।

কিছু নোট:

  • কোডের দৈর্ঘ্য এবং জটিলতা উল্লেখযোগ্যভাবে বৃদ্ধি না করে ত্রুটিগুলি এবং উন্নত কোড সরানো
  • ব্যবহারের স্বাচ্ছন্দ্যের জন্য কার্যকারিতাটিতে কার্যকারিতা রাখুন
  • পপিক্স সামঞ্জস্যপূর্ণ রাখে যাতে তারা (উচিত) সমস্ত পসিক্স শেল (উবুন্টু লিনাক্স 12.04-এ ড্যাশ, ব্যাশ, এবং জেডএস এর সাথে পরীক্ষিত) সাথে কাজ করে
  • গ্লোবাল ভেরিয়েবলগুলি ক্লোবার্বিং এড়াতে এবং বিশ্বব্যাপী নাম স্থান দূষিত করতে এড়াতে স্থানীয় ভেরিয়েবলগুলি ব্যবহৃত হয়
  • উভয় ডিরেক্টরি পাথের অস্তিত্বের প্রয়োজন নেই (আমার আবেদনের জন্য প্রয়োজনীয়তা)
  • পাঠের নামগুলিতে স্পেস, বিশেষ অক্ষর, নিয়ন্ত্রণের অক্ষর, ব্যাকস্ল্যাশ, ট্যাব, ', ",?, *, [,], ইত্যাদি থাকতে পারে may
  • কোর ফাংশন "রিলপথ" কেবলমাত্র পসিক্স শেল অন্তর্নির্মিত ব্যবহার করে তবে প্যারামিটার হিসাবে ক্যানোনিকাল পরম ডিরেক্টরি পথ প্রয়োজন
  • প্রসারিত ফাংশন "রিলপথ" স্বেচ্ছাসেবী ডিরেক্টরি পাথগুলি পরিচালনা করতে পারে (আপেক্ষিক, অ-প্রৌ )়ও) তবে বহিরাগত জিএনইউ কোর ইউটিলিটি "রিডলিঙ্ক" দরকার
  • অন্তর্নির্মিত "প্রতিধ্বনি" এড়ানোর জন্য এবং দুটি কারণের পরিবর্তে বিল্টিন "প্রিন্টফ" ব্যবহার করা হয়েছে:
  • অপ্রয়োজনীয় রূপান্তর এড়ানোর জন্য, শেল এবং ওএস ইউটিলিটিগুলি (যেমন সিডি, এলএন, এলএস, সন্ধান, এম কে ডি ডি; পাইথনের "ওস.প্যাথ.রেলপাথ" এর বিপরীতে যা কিছু ব্যাকস্ল্যাশ ক্রমের ব্যাখ্যা করবে) হিসাবে পাথের নামগুলি ব্যবহার করা হবে এবং প্রত্যাশিত হিসাবে ব্যবহার করা হবে
  • উল্লিখিত ব্যাকস্ল্যাশ সিকোয়েন্স বাদে ফাংশন "রিলপাথ" এর শেষ লাইনটি পাইথনের সাথে সামঞ্জস্যপূর্ণ পথের নামগুলি আউটপুট করে:

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

    শেষ লাইনটি রেখার দ্বারা প্রতিস্থাপন করা যেতে পারে (এবং সরলীকৃত)

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

    আমি পরেরটি পছন্দ করি কারণ

    1. ফাইলের নামগুলি রিলপ্যাথ দ্বারা প্রাপ্ত দির পাথগুলিতে সরাসরি যুক্ত করা যেতে পারে, যেমন:

      ln -s "$(relpath "<fromDir>" "<toDir>")<file>" "<fromDir>"
    2. এই পদ্ধতিতে তৈরি একই দিরের সিম্বলিক লিঙ্কগুলিতে "./"ফাইলের নামটিতে কুরুচিপূর্ণ প্রেন্টেন্ট নেই।

  • যদি আপনার কোনও ত্রুটিটি পাওয়া যায় তবে দয়া করে লিনাক্সবলকে (at) gmail.com এ যোগাযোগ করুন এবং আমি এটি ঠিক করার চেষ্টা করব।
  • রিগ্রেশন টেস্ট স্যুট যুক্ত হয়েছে (এছাড়াও পসিক্স শেল সামঞ্জস্যপূর্ণ)

রিগ্রেশন পরীক্ষার জন্য কোড তালিকা (কেবল শেল স্ক্রিপ্টে এটি যুক্ত করুন):

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

বা আপনি নির্দিষ্ট করতে পারেন যে পথটি কোনও প্রদত্ত ডিরেক্টরি সম্পর্কিত relative

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

এর একটি অসুবিধার জন্য এটি অজগর দরকার, তবে:

  • এটি কোনও অজগর> = 2.6 এ অভিন্নভাবে কাজ করে
  • ফাইল বা ডিরেক্টরি উপস্থিত থাকার প্রয়োজন নেই।
  • ফাইলের নামগুলিতে বিশেষ অক্ষরের বিস্তৃত পরিসর থাকতে পারে। উদাহরণস্বরূপ, ফাইলের নামগুলিতে ফাঁকা জায়গা বা অন্যান্য বিশেষ অক্ষর থাকলে অন্য অনেকগুলি সমাধান কাজ করে না।
  • এটি একটি এক-লাইন ফাংশন যা স্ক্রিপ্টগুলিকে বিশৃঙ্খলা করে না।

মনে রাখবেন যে সমাধানগুলি অন্তর্ভুক্ত রয়েছে basenameবা dirnameঅগত্যা নাও হতে পারে সেগুলি আরও ভাল হওয়া উচিত, কারণ তাদের coreutilsএটি ইনস্টল করা প্রয়োজন। কারও কাছে যদি বিশুদ্ধ bashসমাধান থাকে যা নির্ভরযোগ্য এবং সহজ (বরং একটি সংশ্লেষিত কৌতূহল নয়), আমি অবাক হয়ে যাব।


এটি এখন পর্যন্ত সবচেয়ে দৃ approach় পদ্ধতির মতো বলে মনে হচ্ছে।
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
এটি কাজ করে বলে মনে হচ্ছে। ডিরেক্টরিগুলি যদি বিদ্যমান থাকে তবে ইনপুটগুলিতে $ (রিডলিংক-ফ $ 1) এবং $ (রিডলিঙ্ক -f $ 2) ব্যবহার করলে সমস্যাটি ঠিক করতে পারে যেখানে "" "" অথবা ".." ইনপুটগুলিতে উপস্থিত হয়। ডিরেক্টরিগুলি আসলে না থাকলে এটি কিছুটা সমস্যা তৈরি করতে পারে।
ডাঃ ব্যক্তি ব্যক্তি ২ য়

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

উপর সামান্য উন্নতি kasku এর এবং Pini এর উত্তর, যা ব্যবধান সহ সুন্দর খেলে এবং আপেক্ষিক পাথ ক্ষণস্থায়ী অনুমতি দেয়:

#!/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)
দেবসোলার

2
আপেক্ষিক মানে এই নয় যে ফাইলটি একই ডিরেক্টরিতে রাখতে হবে।
গ্রিনল্ডম্যান

যদিও মূল প্রশ্নটি অনেকগুলি টেস্টকেস সরবরাহ করে না, এই স্ক্রিপ্টটি / হোম / ইউজার 1 থেকে / হোম / ইউজার 2 (সঠিক উত্তর: ../user2) থেকে আপেক্ষিক পথ সন্ধানের মতো সহজ পরীক্ষার জন্য ব্যর্থ। পিনি / জেসিওয়েনজারের স্ক্রিপ্ট এই ক্ষেত্রে কাজ করে।
মাইকেল 25

4

অন্য একটি সমাধান, নিম্নলিখিত প্রসঙ্গে সহজ ব্যবহারের জন্য খাঁটি bash+ জিএনইউ readlink:

ln -s "$(relpath "$A" "$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(ছোট পার্থক্যটি চিহ্নিত করুন), যা ইউআরএল-সিনট্যাক্সের জন্যও কাজ করা উচিত (সুতরাং /কিছু পিছনে টিকে আছে, কিছু- 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

তারপরে আপনি ২ য় প্যারামিটারটি দূরে রাখতে পারেন, যেমন BASE বর্তমান ডিরেক্টরি / ইউআরএল / যা কিছু হোক। এটি যথারীতি কেবল ইউনিক্স নীতি।

যদি আপনি এটি অপছন্দ করেন তবে দয়া করে উইন্ডোজটিতে ফিরে যান। ধন্যবাদ।


3

দুঃখজনকভাবে, মার্ক রুশাকফের উত্তর (এখন মুছে ফেলা হয়েছে - এটি কোডটি এখানে থেকে রেফারেন্স করেছে ) মানিয়ে নেওয়ার সময় সঠিকভাবে কাজ করবে বলে মনে হয় না:

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

বেশিরভাগ ক্ষেত্রে সঠিকভাবে কাজ করার জন্য ভাষ্যটিতে বর্ণিত চিন্তাভাবনা পরিমার্জন করা যেতে পারে। আমি ধরে নিতে চলেছি যে স্ক্রিপ্টটি একটি উত্স আর্গুমেন্ট (আপনি যেখানে আছেন) এবং একটি লক্ষ্য যুক্তি (যেখানে আপনি যেতে চান) নেন এবং উভয়ই পরম পথের নাম বা উভয়ই আপেক্ষিক। যদি একটি পরম এবং অন্যটি আপেক্ষিক হয় তবে সবচেয়ে সহজ কাজটি হ'ল বর্তমান ওয়ার্কিং ডিরেক্টরি সহ আপেক্ষিক নামটির উপসর্গ করা - তবে নীচের কোডটি এটি করে না।


হুঁশিয়ার

নীচের কোডটি সঠিকভাবে কাজ করার কাছাকাছি, তবে একেবারেই সঠিক নয়।

  1. ডেনিস উইলিয়ামসনের মন্তব্যগুলিতে এই সমস্যাটির সমাধান করা হয়েছে।
  2. এখানেও সমস্যা রয়েছে যে পথের নামগুলির এই খাঁটি পাঠ্য প্রক্রিয়াজাতকরণ এবং আপনি অদ্ভুত প্রতিলিপিগুলি দ্বারা গুরুতরভাবে গণ্ডগোল করতে পারেন।
  3. কোড ' xyz/./pqr' এর মতো পথগুলিতে বিপথগামী 'বিন্দু' পরিচালনা করে না ।
  4. কোড ' xyz/../pqr' এর মতো পথগুলিতে বিপথগামী 'ডবল ডটস' পরিচালনা করে না ।
  5. তুচ্ছভাবে: কোডটি ./পথ থেকে অগ্রণী ' ' সরিয়ে দেয় না ।

ডেনিসের কোডটি আরও ভাল কারণ এটি 1 এবং 5 ঠিক করে - তবে একই সমস্যা 2, 3, 4 রয়েছে কারণ ডেনিসের কোডটি ব্যবহার করুন (এবং এর আগে এটি আপ করুন)

(এনবি: পসআইএক্স একটি সিস্টেম কল সরবরাহ করে realpath()যা পাথের নামগুলি সমাধান করে যাতে সেগুলিতে কোনও চিহ্ন থাকে না the ইনপুট নামগুলিতে প্রয়োগ করা, এবং তারপরে ডেনিসের কোড ব্যবহার করে প্রতিবার সঠিক উত্তর দেওয়া যায় the সি কোডটি লিখলে এটি তুচ্ছ that মোড়ক 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 কনফিগার করতে এবং ব্যবহার করার অনুমতি দিয়েছে I আমি বিশ্বাস করি এটি সমর্থন করে /../../ নেটওয়র্ক / হোস্ট / পাথ / চালু / দূরবর্তী / নেটওয়ার্ক / এবং / হোস্ট খুব এটা উইকিপিডিয়া
জনাথন Leffler

পরিবর্তে, আমাদের এখন ইউএনসির দ্বৈত স্ল্যাশ রয়েছে।
পরবর্তী বিজ্ঞপ্তি না হওয়া পর্যন্ত বিরতি দেওয়া হয়েছে।

1
"রিডলিঙ্ক" ইউটিলিটি (কমপক্ষে জিএনইউ সংস্করণ) রিয়েলপথ () এর সমতুল্য করতে পারে, যদি আপনি এটি "-f" বিকল্পটি পাস করেন। উদাহরণস্বরূপ, আমার সিস্টেমে readlink /usr/bin/viদেয় /etc/alternatives/vi, তবে এটি অন্য একটি সিমলিংক - যেখানে readlink -f /usr/bin/viদেয় /usr/bin/vim.basicযা সমস্ত
প্রতিলিপিগুলির

3

আমি আপনার প্রশ্নটিকে "পোর্টেবল" শেল কোডে এটি লেখার চ্যালেঞ্জ হিসাবে গ্রহণ করেছি, অর্থাৎ

  • POSIX শেলটি মাথায় রেখে
  • অ্যারে হিসাবে কোনও বাশিজম নেই
  • প্লেগের মতো বহিরাগতদের ডাকতে এড়িয়ে চলুন। স্ক্রিপ্টে একটি কাঁটাও নেই! এটি একেবারে দ্রুততর করে তোলে, বিশেষত সিগুইনের মতো গুরুত্বপূর্ণ কাঁটাচামচা ওভারহেডযুক্ত সিস্টেমে।
  • পথের নামগুলিতে গ্লোব অক্ষরগুলির সাথে ডিল করতে হবে (*,?, [,])

এটি কোনও পসিক্স কনফর্মেন্ট শেল (zsh, bash, ksh, অ্যাশ, ব্যস্তবক্স, ...) এ চলে। এমনকি এটির ক্রিয়াকলাপ যাচাই করার জন্য এটিতে একটি টেস্টসাইট রয়েছে contains পথের নামগুলির ক্যানোনিকালাইজেশনটি অনুশীলন হিসাবে ছেড়ে গেছে। :-)

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

এই স্ক্রিপ্টটি কেবল পথের নামগুলিতে কাজ করে। এটি বিদ্যমান ফাইলের কোনও প্রয়োজন হয় না। পাস করা পথগুলি যদি নিখুঁত না হয় তবে আচরণটি কিছুটা অস্বাভাবিক, তবে উভয় পথই যদি আপেক্ষিক হয় তবে এটি প্রত্যাশা অনুযায়ী কাজ করা উচিত।

আমি এটি কেবল ওএস এক্সে পরীক্ষা করেছি, সুতরাং এটি পোর্টেবল নাও হতে পারে।

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

এছাড়াও কোডটি কিছুটা অনুলিপি এবং পেস্ট-ইশ, তবে আমার এটি বরং দ্রুত প্রয়োজন needed
juancn

চমৎকার ... ঠিক আমার যা দরকার ছিল। এবং আপনি একটি ক্যানোনিকালাইজিং রুটিন অন্তর্ভুক্ত করেছেন যা আমি দেখেছি এমন বেশিরভাগের চেয়ে ভাল (যা সাধারণত রেজেক্স প্রতিস্থাপনার উপর নির্ভর করে)।
ড্রওয়াটসনকোড

0

এই উত্তরটি প্রশ্নের বাশ অংশটিকে সম্বোধন করে না, তবে ইমাসে এই কার্যকারিতাটি প্রয়োগ করতে আমি এই প্রশ্নের উত্তরগুলি ব্যবহার করার চেষ্টা করেছি বলে আমি এটি এখান থেকে ফেলে দেব।

ইমাকস আসলে বাক্সের বাইরে এটির জন্য একটি ফাংশন রয়েছে:

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
Really {পরম / প্যাটার্ন / স্টাট} কনস্ট্রাক্ট ব্যবহারের কারণে এটি সত্যই বহনযোগ্য নয়, যা পসিক্স নয় (২০১১ হিসাবে)।
জেনস

রেফারেন্সড সোর্স ynform.org/w/Pub/Relpath বেশ কয়েকটি বার স্ক্রিপ্টের বিষয়বস্তু সমেত পুরোপুরি গারড উইকি পৃষ্ঠার দিকে ইঙ্গিত করে, vi টিল্ড লাইনের সাথে মিলিত হয়, কমান্ড সম্পর্কে ত্রুটি বার্তা পাওয়া যায় নি এবং হোয়াট নোট । আসলটি গবেষণা করার জন্য একেবারে অকেজো।
জেনস

-1

আমার এ জাতীয় কিছু দরকার ছিল যা প্রতীকী লিঙ্কগুলিও সমাধান করেছিল। আমি আবিষ্কার করেছি যে পিডব্লুডির সেই উদ্দেশ্যে একটি -P পতাকা রয়েছে। আমার স্ক্রিপ্টের একটি অংশ যুক্ত হয়েছে। এটি শেল স্ক্রিপ্টের একটি ফাংশনের মধ্যে, সুতরাং $ 1 এবং $ 2। ফলাফল মান, যা START_ABS থেকে END_ABS পর্যন্ত আপেক্ষিক পথ, UPDIRS ভেরিয়েবলে রয়েছে। স্ক্রিপ্ট সিডি প্রতিটি প্যারামিটার ডিরেক্টরিতে পিডব্লুডি-পি চালানোর জন্য এবং এর অর্থ এই যে আপেক্ষিক পথের পরামিতিগুলি পরিচালনা করা হয়। চিয়ার্স, জিম

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"
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.