Bash kullanarak geçerli bir dizin verilen mutlak yolu bağıl yola dönüştürme


261

Misal:

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

# Magic

relative="../../bar"

Sihri nasıl yaratabilirim (umarım çok karmaşık olmayan kod ...)?


7
Örneğin (şu anda benim durumum), kaynak yolu değişse bile göreli hata ayıklama bilgileri oluşturabilmesi için gcc göreceli yol vermek için.
Offirmo

U&L'de buna benzer bir soru soruldu: unix.stackexchange.com/questions/100918/… . Cevaplardan biri (@Gilles) , bu sorunun kolay işlenmesini sağlayan sembolik bir araçtan bahseder .
slm

25
Basit: realpath --relative-to=$absolute $current.
kenorb

Yanıtlar:


228

GNU coreutils 8.23'ten realpath kullanmak en basit olanı, sanırım:

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

Örneğin:

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

7
Paketin Ubuntu 14.04'te eski olması ve --relative-to seçeneğinin olmaması üzücü.
kzh

3
Ubuntu
16.04'te

7
$ realpath --relative-to="${PWD}" "$file"geçerli çalışma dizinine göre yollar istiyorsanız kullanışlıdır.
dcoles

1
Bu, /usr/bin/nmap/-yolu içindeki şeyler için doğrudur, ancak aşağıdakiler için değildir /usr/bin/nmap: nmapila /tmp/testingsadece ../../3 kez değil ../. Ancak çalışır, çünkü ..rootfs üzerinde yapmaktır /.
Patrick B.

6
@PatrickB olarak. zımni, --relative-to=…bir dizin bekliyor ve kontrol ETMİYOR. Bu, bir dosyaya göre bir yol talep ederseniz fazladan bir "../" ile sonuçlanacağınız anlamına gelir (bu örnekte göründüğü gibi, /usr/binnadiren veya asla dizin içermediği ve nmapnormalde ikili olduğu için)
IBBoard

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

verir:

../../bar

11
İşe yarıyor ve alternatifleri saçma gösteriyor. Bu benim için bir bonus xD
hasvn

31
+1. Tamam, hile yaptın ... ama bu kullanılmayacak kadar iyi! relpath(){ python -c "import os.path; print os.path.relpath('$1','${2:-$PWD}')" ; }
MestreLion

4
Ne yazık ki, bu evrensel olarak mevcut değildir: os.path.relpath Python 2.6'da yenidir.
Chen Levy

15
@ChenLevy: Python 2.6 2008'de piyasaya sürüldü. 2012'de evrensel olarak mevcut olmadığına inanmak zor.
MestreLion

11
python -c 'import os, sys; print(os.path.relpath(*sys.argv[1:]))'en doğal ve güvenilir şekilde çalışır.
musiphil

31

Bu @pini'den şu anda en iyi puan alan çözümün düzeltilmiş, tamamen işlevsel bir gelişmesidir (ne yazık ki sadece birkaç vakayı ele alır)

Hatırlatma: Dizenin sıfır uzunluğu (= boş) olup olmadığını '-z' testi ve dizenin boş olmaması durumunda '-n' testi .

# 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

Test senaryoları:

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
Offirmo kabuğuna entegre lib github.com/Offirmo/offirmo-shell-lib , fonksiyon «OSL_FILE_find_relative_path» (dosya «osl_lib_file.sh»)
Offirmo

1
+1. Kolayca değiştirerek tüm yolları (/ ile başlayan sadece mutlak yolları) işlemek için yapılabilir source=$1; target=$2ilesource=$(realpath $1); target=$(realpath $2)
Josh Kelley

2
@Josh gerçekten, direktifler var olması şartıyla ... bu birim testleri için elverişsizdi;) Ama gerçek kullanımda evet, realpathtavsiye edilir, source=$(readlink -f $1)vb.
Realpath

Ben tanımladım $sourceve $targetbeğendim: `if [[-e $ 1]]; sonra kaynak = $ (readlink -f $ 1); başka kaynak = $ 1; fi if [[-e $ 2]]; sonra hedef = $ (readlink -f $ 2); başka hedef = $ 2; Bu şekilde, işlev gerçek / varolan göreli yolları ve kurgusal dizinleri tıkayabilir.
Nathan S. Watson-Haigh

1
@ NathanS.Watson-Haigh Daha da iyisi, son zamanlarda sadece bunu yapan readlinkbir -mseçenek olduğunu keşfettim ;)
Offirmo

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

Harika komut dosyası - kısa ve temiz. Bir düzenleme uyguladım (akran incelemesini bekliyorum): common_part = $ source / common_part = $ (dirname $ common_part) / echo $ {back} $ {target # $ common_part} Mevcut komut dosyası, dizin adının başındaki uygunsuz eşleşme nedeniyle başarısız olur karşılaştırırken, örneğin: "/ foo / bar / baz" ile "/ foo / barsucks / bonk". Eğik çizgiyi değişkene ve son değerlendirmenin dışına taşımak bu hatayı düzeltir.
jcwenger

3
Bu komut dosyası işe yaramıyor. Basit bir "bir dizin aşağı" testlerinde başarısız olur. Jcwenger tarafından yapılan düzenlemeler biraz daha iyi çalışır ancak fazladan bir "../" ekleme eğilimindedir.
Dr. Person Person II

1
argüman üzerinde bir "/" işareti varsa bazı durumlarda benim için başarısız olur; örneğin, $ 1 = "$ HOME /" ve $ 2 = "$ HOME / temp" ise, "/ home / user / temp /" döndürür, ancak $ 1 = $ HOME ise, "temp" göreli yolunu düzgün bir şekilde döndürür. Bu nedenle, hem kaynak = $ 1 hem de hedef = $ 2, sed kullanılarak (veya bash değişken ikamesi kullanılarak "temizlenebilir", ancak => kaynak = $ (echo "$ {1}" | sed 's / \ / * $ // ')
michael

1
Küçük iyileştirme: Kaynak / hedefi doğrudan 1 $ ve 2 $ olarak ayarlamak yerine şunu yapın: source = $ (cd $ 1; pwd) target = $ (cd $ 2; pwd). Bu şekilde yolları ele alır. ve .. doğru.
Joseph Garvin

4
En çok oy alan cevap olmasına rağmen, bu cevabın birçok sınırlaması vardır, bu nedenle diğer birçok cevap gönderilmektedir. Bunun yerine diğer cevaplara, özellikle de test senaryolarını gösteren yanıtlara bakın. Ve lütfen bu yorumu oylayın!
Offirmo

25

Perl'de 2001'den beri yerleşiktir , bu yüzden hayal edebileceğiniz hemen hemen her sistemde, hatta VMS'de çalışır .

perl -e 'use File::Spec; print File::Spec->abs2rel(@ARGV) . "\n"' FILE BASE

Ayrıca, çözümü anlamak kolaydır.

Yani örneğiniz için:

perl -e 'use File::Spec; print File::Spec->abs2rel(@ARGV) . "\n"' $absolute $current

... iyi çalışır.


3
sayperl'de log olarak mevcut değildir, ancak burada etkin bir şekilde kullanılabilir. perl -MFile::Spec -E 'say File::Spec->abs2rel(@ARGV)'
William Pursell

+1 ancak daha eski olan bu benzer yanıta da bakınız (Şub 2012). William Pursell'in ilgili yorumlarını da okuyun . Sürümüm iki komut satırıdır: perl -MFile::Spec -e 'print File::Spec->abs2rel(@ARGV)' "$target"ve perl -MFile::Spec -e 'print File::Spec->abs2rel(@ARGV)' "$target" "$origin". İlk tek satırlık perl betiği bir bağımsız değişken kullanır (başlangıç ​​noktası geçerli çalışma dizinidir). İkinci bir satırlık perl betiği iki bağımsız değişken kullanır.
olibre

3
Kabul edilen cevap bu olmalı. perlhemen hemen her yerde bulunabilir, ancak cevap hala tek astarlıdır.
Dmitry Ginzburg

19

Yüklediğinizi varsayarsak: bash, pwd, dirname, echo; o zaman yardım

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

Ben cevabını golfed ettik pini ve birkaç diğer fikirler

Not : Bu, her iki yolun da mevcut klasörler olmasını gerektirir. Dosyalar olacak değil çalışır.


2
ideal cevap: / bin / sh ile çalışır, readlink, python, perl gerektirmez -> ışık / gömülü sistemler veya windows bash konsolu için harika
Francois

2
Ne yazık ki, bu her zaman arzu edilmeyen yolun mevcut olmasını gerektirir.
drwatsoncode

Tanrısal cevap. Cd-pwd şeyler sanırım bağlantıları çözmek için mi? Güzel golf!
Teck-ucube

15

Python var os.path.relpathbir kabuk işlevi olarak

Bu relpathegzersizin amacı, xnios.path.relpath tarafından önerildiği gibi Python 2.7'nin işlevini (Python 2.6 sürümünden edinilebilir, ancak yalnızca 2.7'de düzgün çalışarak) taklit etmektir . Sonuç olarak, bazı sonuçlar diğer cevaplarda verilen işlevlerden farklı olabilir.

( python -cZSH'den çağrıya dayanan doğrulamayı bozduğu için yollardaki yeni satırlarla test etmedim . Kesinlikle biraz çaba ile mümkün olacaktır.)

Bash'deki “sihir” ile ilgili olarak, uzun zaman önce Bash'de sihir aramayı bıraktım, ama o zamandan beri ihtiyacım olan tüm sihri ve sonra bazılarını ZSH'de buldum.

Sonuç olarak, iki uygulama öneriyorum.

İlk uygulama tamamen POSIX uyumlu olmayı amaçlamaktadır . /bin/dashDebian 6.0.6 “Squeeze” ile test ettim . Aynı zamanda mükemmel çalışır/bin/sh aslında bir POSIX kabuğu gibi davranan Bash sürüm 3.2 olan OS X 10.8.3 .

İkinci uygulama, çoklu eğik çizgilere ve yollardaki diğer rahatsızlıklara karşı dayanıklı olan bir ZSH kabuk işlevidir. ZSH'niz varsa, #!/usr/bin/env zshbaşka bir kabuktan aşağıda sunulan komut dosyası formunda (yani bir şeyle ) çağırsanız bile bu önerilen sürümdür .

Son olarak, diğer yanıtlarda verilen test senaryolarında verilen relpathkomutun çıktısını doğrulayan bir ZSH betiği yazdım $PATH. ! ? *Buradaki gibi bazı boşluklar, sekmeler ve noktalama işaretleri ekleyerek bu testlere biraz baharat ekledim ve ayrıca vim-powerline'da bulunan egzotik UTF-8 karakterleri ile başka bir test daha attım .

POSIX kabuk işlevi

İlk olarak, POSIX uyumlu kabuk işlevi. Çeşitli yollarla çalışır, ancak birden fazla eğik çizgiyi temizlemez veya sembolik bağlantıları çözmez.

#!/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 kabuk işlevi

Şimdi, daha sağlam bir zshversiyon. Argümanları gerçek yollarla realpath -f(Linux coreutilspaketinde mevcut) çözümlemek istiyorsanız , :a3. ve 4. satırları:A .

Bunu zsh içinde kullanmak için, ilk ve son satırı kaldırın ve $FPATHdeğişkeninizdeki bir dizine koyun .

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

Test komut dosyası

Son olarak, test komut dosyası. -vAyrıntılı çıktıyı etkinleştirmek için bir seçeneği kabul eder .

#!/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
/Korkarım ilk yol bittiğinde çalışmıyor .
Noldorin

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")"; }

Kabuk betiğinin üstünde pini esinlenildi (Teşekkürler!). Yığın Taşması sözdizimi vurgulama modülünde bir hata tetikler (en azından önizleme çerçevemde). Vurgulama yanlışsa lütfen dikkate almayın.

Bazı notlar:

  • Kod uzunluğunu ve karmaşıklığını önemli ölçüde artırmadan hatalar ve geliştirilmiş kod kaldırıldı
  • Kullanım kolaylığı için işlevselliği işlevlere yerleştirin
  • Tüm POSIX kabuklarıyla (Ubuntu Linux 12.04'te tire, bash ve zsh ile test edilmiş) çalışması için POSIX uyumlu işlevler korunur
  • Yerel değişkenleri yalnızca global değişkenleri gizlemek ve küresel ad alanını kirletmemek için kullandım
  • Her iki dizin yolunun da olması gerekmez (uygulamam için gereksinim)
  • Yol adları boşluklar, özel karakterler, kontrol karakterleri, ters eğik çizgiler, sekmeler, ', ",?, *, [,] Vb. İçerebilir.
  • Temel işlev "relPath" yalnızca POSIX kabuk yerleşiklerini kullanır, ancak parametre olarak kurallı mutlak dizin yolları gerektirir
  • Genişletilmiş işlev "relpath" isteğe bağlı dizin yollarını (göreceli, standart olmayan) işleyebilir ancak harici GNU çekirdek yardımcı programı "readlink" gerektirir
  • Yerleşik "echo" dan kaçınıldı ve bunun yerine iki nedenden dolayı yerleşik "printf" kullanıldı:
  • Gereksiz dönüşümleri önlemek için, yol adları kabuk ve işletim sistemi yardımcı programları (ör. Cd, ln, ls, find, mkdir; bazı ters eğik çizgi dizilerini yorumlayacak olan "os.path.relpath" in aksine, döndürüldükçe ve beklendiği gibi kullanılır.
  • Bahsedilen ters eğik çizgi dizileri dışında, "relPath" işlevinin son satırı, python ile uyumlu yol adları verir:

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

    Son satır, satır ile değiştirilebilir (ve basitleştirilebilir)

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

    İkincisini tercih ederim çünkü

    1. Dosya adları relPath tarafından elde edilen dir yollarına doğrudan eklenebilir, örneğin:

      ln -s "$(relpath "<fromDir>" "<toDir>")<file>" "<fromDir>"
    2. Bu yöntemle oluşturulan aynı direkteki sembolik bağlar, çirkin "./"dosya adının başına eklenmez.

  • Bir hata bulursanız lütfen linuxball (at) gmail.com ile iletişime geçin ve düzeltmeye çalışacağım.
  • Regresyon test paketi eklendi (ayrıca POSIX kabuğu uyumlu)

Regresyon testleri için kod listesi (basitçe kabuk betiğine ekleyin):

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

Buradaki cevapların çoğu günlük kullanım için pratik değildir. Bunu saf bash'da düzgün yapmak çok zor olduğu için, aşağıdaki güvenilir çözümü öneririm (bir yorumda gömülü bir öneriye benzer):

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

Ardından, geçerli dizini temel alarak ilgili yolu alabilirsiniz:

echo $(relpath somepath)

veya yolun belirli bir dizine göreli olacağını belirtebilirsiniz:

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

Tek dezavantajı, bu python gerektirir, ancak:

  • Herhangi bir python'da aynı şekilde çalışır> = 2.6
  • Dosyaların veya dizinlerin mevcut olmasını gerektirmez.
  • Dosya adları daha geniş bir dizi özel karakter içerebilir. Örneğin, dosya adları boşluk veya başka özel karakterler içeriyorsa diğer pek çok çözüm çalışmaz.
  • Komut dosyalarını karıştırmayan tek satırlık bir işlevdir.

Kurulum gerektirdiğinden, içerebilecek basenameveya dirnamegerekmeyebilecek çözümlerin daha iyi olabileceğini unutmayın coreutils. Birinin bashgüvenilir ve basit (kıvrık bir meraktan ziyade) saf bir çözümü varsa, şaşırırdım.


Bu şimdiye kadarki en sağlam yaklaşım gibi görünüyor.
dimo414

7

Bu komut sadece olmadan mutlak yol veya göreli yolları olan girişler için doğru sonuçlar verir .ya da ..:

#!/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
Bu işe yarıyor gibi görünüyor. Dizinler gerçekten mevcutsa, girdilerde $ (readlink -f $ 1) ve $ (readlink -f $ 2) kullanımı "." veya girişlerde ".." görünür. Bu, dizinler gerçekten yoksa bazı sorunlara neden olabilir.
Dr. Kişi Kişi II

7

Perl'i bu kadar önemsiz olmayan görev için kullanırım:

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

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

1
+1, ancak tavsiye eder: perl -MFile::Spec -e "print File::Spec->abs2rel('$absolute','$current')"böylece mutlak ve akım alıntılanır.
William Pursell

Sevdim relative=$(perl -MFile::Spec -e 'print File::Spec->abs2rel(@ARGV)' "$absolute" "$current"). Bu, değerlerin kendilerinin perl kodu içerememelerini sağlar!
Erik Aronesty

6

Kasku ve Pini'nin cevaplarında, boşluklarla daha güzel oynayan ve göreceli yollardan geçmeye izin veren hafif bir iyileştirme :

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

$ ./test.sh 
TEST=/home/ubuntu/.//blah
TMP=/home/ubuntu/blah
REL=blah

Kompaktlık ve baslık için +1. Ancak, aynı zamanda aramalısınız readlinküzerinde $(pwd).
DevSolar

2
Göreli, dosyanın aynı dizine yerleştirilmesi gerektiği anlamına gelmez.
greenoldman

orijinal soru çok fazla testcases sağlamaz, ancak bu komut dosyası / home / user1 ile / home / user2 arasındaki bağıl yolu bulmak gibi basit testlerde başarısız olur (doğru cevap: ../user2). Pini / jcwenger komut dosyası bu durumda çalışır.
michael

4

Yine başka bir çözüm, aşağıdaki bağlamda kolay kullanım için saf bash+ GNU readlink:

ln -s "$(relpath "$A" "$B")" "$B"

Düzenleme: "$ B" mevcut olmadığından veya bu durumda hiçbir softlink olmadığından emin olun, başka relpathne istediğiniz bu bağlantıyı izler!

Bu hemen hemen tüm Linux'ta çalışır. Yanınızda readlink -mçalışmıyorsa, readlink -fbunun yerine deneyin . Olası güncellemeler için ayrıca bkz. 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:-.}"
}

Notlar:

  • Dosya adlarının *veya içermesi durumunda istenmeyen kabuk meta karakteri genişlemesine karşı güvenli olmasına dikkat edildi ?.
  • Çıktı, şu ilk argüman olarak kullanılabilir ln -s:
    • relpath / /.boş dize verir, verir
    • relpath a abir dizin aolsa bile verira
  • En yaygın vakalar da makul sonuçlar vermek için test edildi.
  • Bu çözüm dize öneki eşleşmesini kullanır, readlink yolları standartlaştırmak için gereklidir.
  • Bu sayede readlink -mhenüz mevcut olmayan yollar için de çalışıyor.

Nerede eski sistemlerde, readlink -mkullanılamaz, readlink -fdosya yoksa başarısız olur. Yani muhtemelen böyle bir geçici çözüme ihtiyacınız var (denenmemiş!):

readlink_missing()
{
readlink -m -- "$1" && return
readlink -f -- "$1" && return
[ -e . ] && echo "$(readlink_missing "$(dirname "$1")")/$(basename "$1")"
}

Bu durumda gerçekten oldukça doğru değil $1kapsar .veya ..benzeri (yolları varolmayan için/doesnotexist/./a ), ancak çoğu durumlarda kapsamalıdır.

( readlink -m --Yukarıda değiştirinreadlink_missing .)

Aşağı oy takip ettiği için düzenle

İşte bir test, bu fonksiyonun gerçekten doğru olduğu:

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"

Şaşıran? Eh, bu doğru sonuçlardır ! Soruna uymadığını düşünseniz bile, bunun doğru olduğuna dair kanıt:

check "http://example.com/foo/baz/moo" "http://example.com/foo/bar" "../bar"

Kuşkusuz, ../barsayfanın sayfadan bargörülen tam ve doğru bağıl yolumoo . Diğer her şey basit yanlış olurdu.

Görünüşe göre, bu currentbir dizin olduğu sorusunun çıktısını kabul etmek önemsizdir :

absolute="/foo/bar"
current="/foo/baz/foo"
relative="../$(relpath "$absolute" "$current")"

Bu tam olarak geri döner, ne istenir.

Ve bir kaş kaldırmadan önce, relpathURL-Sözdizimi için de çalışması gereken ( daha küçük bir fark var), /biraz bash-magic sayesinde bir iz hayatta kalır ):

# 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:-.}"
}

Ve işte sadece açıklığa kavuşturmak için kontroller: Gerçekten söylendiği gibi çalışıyor.

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

Ve bu sorudan istenen sonucu vermek için nasıl kullanılabilir:

absolute="/foo/bar"
current="/foo/baz/foo"
relative="$(relpathurl "$absolute" "$current/")"
echo "$relative"

Çalışmayan bir şey bulursanız, lütfen aşağıdaki yorumlarda bana bildirin. Teşekkürler.

Not:

Neden argümanlar relpathBuradaki diğer cevapların aksine "tersine çevrildi" ?

Eğer değiştirirsen

Y="$(readlink -m -- "$2")" || return

için

Y="$(readlink -m -- "${2:-"$PWD"}")" || return

BASE'in geçerli dizin / URL / ne olursa olsun 2. parametreyi uzak bırakabilirsiniz. Bu her zamanki gibi sadece Unix ilkesidir.

Bunu beğenmediyseniz, lütfen Windows'a geri dönün. Teşekkürler.


3

Ne yazık ki, Mark Rushakoff'un cevabı (şimdi silindi - buradan kodu referans alıyor ) aşağıdakilere uyarlandığında doğru çalışmıyor gibi görünüyor:

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

Yorumda ana hatları çizilen düşünce, çoğu vaka için doğru çalışmasını sağlamak üzere geliştirilebilir. Komut dosyası bir kaynak bağımsız değişken (nerede olduğunuzu) ve bir hedef bağımsız değişken (nereye almak istediğiniz) alır ve her ikisi de mutlak yol adları ya da her ikisi de göreli olduğunu varsayalım. Biri mutlak ve diğeri göreli ise, en kolay şey göreli adı geçerli çalışma dizinine önek olarak eklemektir - ancak aşağıdaki kod bunu yapmaz.


Dikkat

Aşağıdaki kod düzgün çalışmaya yakın, ancak doğru değil.

  1. Dennis Williamson'ın yorumlarında sorun var.
  2. Yol adlarının tamamen metinsel olarak işlenmesinin ve garip semboliklerle ciddi şekilde karıştırılabileceğiniz bir sorun da var.
  3. Kod ' xyz/./pqr' gibi yollarda başıboş 'noktalar' işlemez .
  4. Kod, ' xyz/../pqr' gibi yollarda başıboş 'çift noktaları' işlemez .
  5. Önemsiz: Kod, yoldaki ' ./' satır başını kaldırmaz .

Dennis'in kodu daha iyidir, çünkü 1 ve 5'i düzeltir - ancak aynı sorunları 2, 3, 4'e sahiptir. Bu nedenle Dennis'in kodunu kullanın (ve bundan önce oy verin).

(Not: POSIX, realpath()yol adlarını, içinde hiçbir sembolik bağlantı kalmayacak şekilde çözen bir sistem çağrısı sağlar . Bunu giriş adlarına uygulamak ve daha sonra Dennis kodunu kullanmak her seferinde doğru cevabı verir. sarar realpath()- Yaptım - ama bunu yapan standart bir yardımcı program bilmiyorum.)


Bunun için, Perl'i kabuğundan daha kolay buluyorum, ancak bash diziler için iyi bir desteğe sahip ve muhtemelen bunu da yapabilir - okuyucu için egzersiz. Dolayısıyla, iki uyumlu ad verildiğinde, her birini bileşenlere ayırın:

  • Göreli yolu boş olarak ayarlayın.
  • Bileşenler aynı olsa da, bir sonrakine geçin.
  • Karşılık gelen bileşenler farklı olduğunda veya bir yol için daha fazla bileşen olmadığında:
  • Kalan kaynak bileşeni yoksa ve göreli yol boşsa "." başlangıç.
  • Kalan her kaynak bileşen için, göreli yolun başına "../" ekleyin.
  • Kalan hedef bileşen yoksa ve göreli yol boşsa "." başlangıç.
  • Kalan her hedef bileşen için, bileşeni eğik çizgiden sonra yolun sonuna ekleyin.

Böylece:

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

Test komut dosyası (köşeli parantezler boşluk ve sekme içerir):

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

Test komut dosyasından çıktı:

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

Bu Perl betiği, garip girdiler karşısında Unix (Windows yol adlarının tüm karmaşıklıklarını dikkate almaz) üzerinde oldukça iyi çalışır. Var olan adların gerçek yolunu çözmek için modülü Cwdve işlevini kullanır ve realpathvar olmayan yollar için metinsel bir analiz yapar. Biri hariç tüm durumlarda, Dennis'in senaryosu ile aynı çıktıyı üretir. Sapkın durum:

source   = home/part1/part2
target   = .
relpath1 = ../../..
relpath2 = ../../../.

İki sonuç eşdeğerdir - aynı değildir. (Çıktı, test komut dosyasının hafifçe değiştirilmiş bir sürümündedir - aşağıdaki Perl komut dosyası, yukarıdaki komut dosyasında olduğu gibi girdiler ve yanıtı değil, yanıtı yazdırır.) Şimdi: çalışmayan yanıtı ortadan kaldırmalı mıyım? Olabilir...

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

Sizin /home/part1/part2için /çok fazla birine sahiptir ../. Aksi takdirde, benim komut dosyası çıktı ile eşleşir, ancak benimki .hedefin bulunduğu yere gereksiz ekler .ve ./yukarı çıkmadan inenlerin başında a kullanmıyorum .
sonraki duyuruya kadar duraklatıldı.

@Dennis: Sonuçlara göz gezdirmek için zaman harcadım - bazen bu sorunu görebildim ve bazen tekrar bulamadım. Baştaki bir './' işaretinin kaldırılması başka bir önemli adımdır. 'Gömülü değil' hakkındaki yorumunuz. veya .. 'da uygundur. İşi düzgün yapmak aslında şaşırtıcı derecede zordur - iki kat, yani isimlerden herhangi biri aslında bir sembolik ise; ikimiz de tamamen metinsel analiz yapıyoruz.
Jonathan Leffler

@Dennis: Elbette, Newcastle Connection ağınız yoksa, kökün üstüne çıkmaya çalışmak boştur, bu yüzden ../../../ .. ve ../../ .. eşdeğerdir. Ancak, bu saf kaçıştır; eleştiriniz doğru. (Newcastle Connection, farklı bir ana bilgisayara ulaşmak için /../host/path/on/remote/machine gösterimini yapılandırmanıza ve kullanmanıza izin verdi - temiz bir şema. Sanırım /../../network/host/ path / on / remote / network / ve / host da. Wikipedia'da.)
Jonathan Leffler

Bunun yerine, şimdi UNC'nin çift eğik çizgisi var.
sonraki duyuruya kadar duraklatıldı.

1
"Readlink" yardımcı programı (en azından GNU sürümü) "-f" seçeneğini iletirseniz realpath () işlevine eşdeğer olabilir. Örneğin, benim sistemimde readlink /usr/bin/viverir /etc/alternatives/vi, ama bu başka bir sembolik - tüm semboliklerin nihai hedefi olan readlink -f /usr/bin/viverir /usr/bin/vim.basic...
psmears

3

Sorunuzu "taşınabilir" kabuk kodunda yazmak için bir meydan okuma olarak aldım, yani

  • POSIX kabuğu göz önünde bulundurularak
  • diziler gibi bashisms yok
  • veba gibi dışsalları aramaktan kaçının. Senaryoda tek bir çatal yok! Bu, özellikle cygwin gibi önemli çatal yükü olan sistemlerde inanılmaz derecede hızlı hale getirir.
  • Yol adlarındaki (*,?, [,]) Glob karakterlerle ilgilenilmelidir

Herhangi bir POSIX uyumlu kabuk (zsh, bash, ksh, ash, busybox, ...) üzerinde çalışır. Hatta çalıştığını doğrulamak için bir testsuit içerir. Yol adlarının kanonikleştirilmesi bir alıştırma olarak bırakılmıştır. :-)

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

Çözümüm:

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

}

Bu son derece portatif :)
Anonim

2

İşte benim versiyonum. Bu dayanıyor cevap tarafından @Offirmo . Dash uyumlu hale getirdim ve aşağıdaki testcase hatasını düzelttim:

./compute-relative.sh "/a/b/c/de/f/g" "/a/b/c/def/g/" -> "../..f/g/"

Şimdi:

CT_FindRelativePath "/a/b/c/de/f/g" "/a/b/c/def/g/" -> "../../../def/g/"

Kodu inceleyin:

# 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

Sanırım bu da hile yapacak ... (dahili testlerle birlikte gelir) :)

Tamam, biraz tepegöz bekleniyor, ama biz burada Bourne kabuğu yapıyoruz! ;)

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

Bu komut dosyası yalnızca yol adlarında çalışır. Dosyaların varlığını gerektirmez. Geçilen yollar mutlak değilse, davranış biraz sıra dışıdır, ancak her iki yol da göreceli ise beklendiği gibi çalışmalıdır.

Sadece OS X'te test ettim, bu yüzden taşınabilir olmayabilir.

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

Ayrıca, kod biraz kopyala & yapıştır-ish, ama bu oldukça hızlı bir şekilde gerekli.
juancn

Güzel ... tam da ihtiyacım olan şey. Ve gördüğüm diğerlerinden daha iyi bir kanonikleştirici rutin eklediniz (genellikle normal ifadelerin yerine geçiyor).
drwatsoncode

0

Bu cevap, sorunun Bash kısmını ele almıyor, ancak Emacs'ta bu işlevselliği uygulamak için bu sorudaki cevapları kullanmaya çalıştığımdan, onu oraya atacağım.

Emacs aslında bunun için bir işleve sahiptir:

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"

Yakın zamanda eklediğim python cevabının ( relpathişlev) file-relative-namesağladığınız test senaryoları için aynı şekilde davrandığına inanıyorum .
Gary Wisniewski

-1

İşte diğer programları çağırmadan bunu yapan bir kabuk betiği:

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

kaynak: http://www.ynform.org/w/Pub/Relpath


1
POSIX olmayan $ {param / pattern / subst} yapısının kullanımı nedeniyle bu gerçekten taşınabilir değildir (2011 itibariyle).
Jens

Başvurulan kaynak ynform.org/w/Pub/Relpath , birkaç kez komut dosyası içeriğini içeren, vi tilde satırlarıyla, bulunmayan komutlarla ilgili hata mesajlarıyla ve ne olursa olsun , tamamen bozuk bir wiki sayfasına işaret eder. Orijinali araştıran biri için tamamen işe yaramaz.
Jens

-1

Böyle bir şeye ihtiyacım vardı ama sembolik bağlantıları da çözdüm. Pwd'nin bu amaçla -P bayrağı olduğunu keşfettim. Senaryomun bir parçası eklenir. Bir kabuk komut dosyasındaki bir işlev içinde, bu nedenle $ 1 ve $ 2. START_ABS ile END_ABS arasındaki göreli yol olan sonuç değeri UPDIRS değişkenindedir. Komut dosyası cd'leri pwd -P'yi yürütmek için her parametre dizinine yerleştirilir ve bu aynı zamanda göreli yol parametrelerinin işlendiği anlamına gelir. Şerefe Jim

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"
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.