Mendapatkan tautan relatif antara dua jalur

21

Katakanlah saya punya dua jalur: <source_path>dan <target_path>. Saya ingin shell saya (zsh) secara otomatis mencari tahu apakah ada cara untuk mewakili <target_path>dari <source_path>sebagai jalur relatif.

Misalnya, mari kita asumsikan

  • <source_path> aku s /foo/bar/something
  • <target_path> aku s /foo/hello/world

Hasilnya adalah ../../hello/world

Mengapa saya membutuhkan ini:

Saya perlu ingin membuat symbolic link dari <source_path>ke <target_path>menggunakan relatif symbolic link bila memungkinkan, karena jika server samba kami tidak menunjukkan file dengan benar ketika saya mengakses file-file pada jaringan dari Windows (Saya bukan admin sys, dan don' t memiliki kendali atas pengaturan ini)

Dengan asumsi itu <target_path>dan <source_path>merupakan jalur absolut, berikut ini menciptakan tautan simbolik yang menunjuk ke jalur absolut .

ln -s <target_path> <source_path>

jadi tidak berfungsi untuk kebutuhan saya. Saya perlu melakukan ini untuk ratusan file, jadi saya tidak bisa memperbaikinya secara manual.

Adakah shell built-in yang menangani ini?

Amelio Vazquez-Reina
sumber
Anda dapat menggunakan symlinksuntuk mengonversi tautan absolut ke relatif.
Stéphane Chazelas
@StephaneChazelas bagaimana? Bisakah Anda memposting jawaban yang menjelaskan?
terdon

Jawaban:

10

Anda bisa menggunakan symlinksperintah untuk mengubah jalur absolut menjadi relatif:

/tmp$ mkdir -p 1/{a,b,c} 2
/tmp$ cd 2
/tmp/2$ ln -s /tmp/1/* .
/tmp/2$ ls -l
total 0
lrwxrwxrwx 1 stephane stephane 8 Jul 31 16:32 a -> /tmp/1/a/
lrwxrwxrwx 1 stephane stephane 8 Jul 31 16:32 b -> /tmp/1/b/
lrwxrwxrwx 1 stephane stephane 8 Jul 31 16:32 c -> /tmp/1/c/

Kami memiliki tautan absolut, mari ubah menjadi relatif:

/tmp/2$ symlinks -cr .
absolute: /tmp/2/a -> /tmp/1/a
changed:  /tmp/2/a -> ../1/a
absolute: /tmp/2/b -> /tmp/1/b
changed:  /tmp/2/b -> ../1/b
absolute: /tmp/2/c -> /tmp/1/c
changed:  /tmp/2/c -> ../1/c
/tmp/2$ ls -l
total 0
lrwxrwxrwx 1 stephane stephane 6 Jul 31 16:32 a -> ../1/a/
lrwxrwxrwx 1 stephane stephane 6 Jul 31 16:32 b -> ../1/b/
lrwxrwxrwx 1 stephane stephane 6 Jul 31 16:32 c -> ../1/c/

Referensi

Stéphane Chazelas
sumber
Terima kasih Stephane. Ini menyelesaikan masalah root, jadi saya telah menerimanya. Yang mengatakan, akan lebih baik untuk memiliki sesuatu (misalnya fungsi zsh) yang mengambil dua jalur (sumber dan target) dan mengeluarkan jalur relatif dari sumber ke target (tidak ada tautan simbolis di sini). Itu akan menyelesaikan pertanyaan yang diajukan dalam judul juga.
Amelio Vazquez-Reina
@ user815423426, terdon telah menjawab bagian itu
Stéphane Chazelas
24

Coba gunakan realpathperintah (bagian dari GNU coreutils; > = 8.23 ), misalnya:

realpath --relative-to=/foo/bar/something /foo/hello/world

Jika Anda menggunakan macOS, instal versi GNU melalui: brew install coreutilsdan gunakan grealpath.

Perhatikan bahwa kedua jalur harus ada agar perintah berhasil. Jika Anda tetap membutuhkan jalur relatif walaupun salah satunya tidak ada, tambahkan -m switch.

Untuk contoh lebih lanjut, lihat Mengkonversi jalur absolut menjadi jalur relatif yang diberikan direktori saat ini .

kenorb
sumber
Saya mendapatkan realpath: unrecognized option '--relative-to=.':( versi realpath 1.19
phuclv
@ LưuVĩnhPhúc Anda harus menggunakan versi GNU / Linux realpath. Jika Anda berada di MacOS, menginstal melalui: brew install coreutils.
kenorb
tidak, saya di Ubuntu 14.04 LTS
phuclv
@ LưuVĩnhPhúc Saya berada di realpath (GNU coreutils) 8.26tempat parameternya berada. Lihat: gnu.org/software/coreutils/realpath Periksa kembali apakah Anda tidak menggunakan alias (mis. Jalankan sebagai \realpath), dan bagaimana Anda dapat menginstal versi dari coreutils.
kenorb
7

Saya pikir solusi python ini (diambil dari pos yang sama dengan jawaban @ EvanTeitelman) juga layak disebut. Tambahkan ini ke Anda ~/.zshrc:

relpath() python -c 'import os.path, sys;\
  print os.path.relpath(sys.argv[1],sys.argv[2])' "$1" "${2-$PWD}"

Anda kemudian dapat melakukannya, misalnya:

$ relpath /usr/local/share/doc/emacs /usr/local/share/fonts
../doc/emacs
terdon
sumber
@StephaneChazelas ini adalah salinan langsung dari jawaban yang ditautkan. Saya seorang pria Perl dan pada dasarnya tidak tahu python jadi saya tidak tahu bagaimana menggunakannya sys.argv. Jika kode yang diposting salah atau dapat ditingkatkan, jangan ragu untuk melakukannya dengan terima kasih.
terdon
@StephaneChazelas saya mengerti sekarang, terima kasih atas hasil editnya.
terdon
7

Tidak ada builtin shell untuk menangani ini. Saya menemukan solusi pada Stack Overflow . Saya telah menyalin solusinya di sini dengan sedikit modifikasi dan menandai jawaban ini sebagai komunitas wiki.

relpath() {
    # both $1 and $2 are absolute paths beginning with /
    # $1 must be a canonical path; that is none of its directory
    # components may be ".", ".." or a symbolic link
    #
    # returns relative path to $2/$target from $1/$source
    source=$1
    target=$2

    common_part=$source
    result=

    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#?}
    fi

    printf '%s\n' "$result"
}

Anda dapat menggunakan fungsi ini seperti:

source=/foo/bar/something
target=/foo/hello/world
ln -s "$(relpath "$source" "$target")" "$source"
pengguna26112
sumber
3
# Prints out the relative path between to absolute paths. Trivial.
#
# Parameters:
# $1 = first path
# $2 = second path
#
# Output: the relative path between 1st and 2nd paths
relpath() {
    local pos="${1%%/}" ref="${2%%/}" down=''

    while :; do
        test "$pos" = '/' && break
        case "$ref" in $pos/*) break;; esac
        down="../$down"
        pos=${pos%/*}
    done

    echo "$down${ref##$pos/}"
}

Ini adalah solusi paling sederhana dan paling indah untuk masalah ini.

Juga harus bekerja pada semua cangkang seperti bourne.

Dan kebijaksanaan di sini adalah: sama sekali tidak perlu utilitas eksternal atau bahkan kondisi rumit. Beberapa pengganti awalan / sufiks dan loop sementara adalah semua yang Anda butuhkan untuk menyelesaikan apa saja yang dilakukan pada cangkang mirip-bourne.

Juga tersedia melalui PasteBin: http://pastebin.com/CtTfvime

Arto Pekkanen
sumber
Terima kasih atas solusinya. Saya menemukan bahwa untuk membuatnya berfungsi dalam, Makefilesaya perlu menambahkan sepasang tanda kutip ganda ke baris pos=${pos%/*}(dan, tentu saja, titik koma dan garis miring terbalik di akhir setiap baris).
Circonflexe
Ide ini tidak buruk tapi, dalam versi banyak saat ini kasus tidak bekerja: relpath /tmp /tmp, relpath /tmp /tmp/a, relpath /tmp/a /. Di tambah, Anda lupa menyebutkan bahwa semua jalur harus absolut DAN dikanonikkan (mis. /tmp/a/..Tidak berfungsi)
Jérôme Pouiller
0

Saya harus menulis skrip bash untuk melakukan ini dengan andal (dan tentu saja tanpa memverifikasi keberadaan file).

Pemakaian

relpath <symlink path> <source path>

Mengembalikan jalur sumber relatif.

contoh:

#/a/b/c/d/e   ->  /a/b/CC/DD/EE       ->  ../../CC/DD/EE
relpath /a/b/c/d/e   /a/b/CC/DD/EE

#./a/b/c/d/e   ->  ./a/b/CC/DD/EE       ->  ../../CC/DD/EE
relpath ./a/b/c/d/e  ./a/b/CC/DD/EE

keduanya dapatkan ../../CC/DD/EE


Untuk melakukan tes cepat, Anda dapat menjalankan

curl -sL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath | bash -s -- --test

hasil: daftar tes

/a             ->  /                 ->  .
/a/b           ->  /                 ->  ..
/a/b           ->  /a                ->  .
/a/b/c         ->  /a                ->  ..
/a/b/c         ->  /a/b              ->  .
/a/b/c         ->  /a/b/CC           ->  CC
/a/b/c/d/e     ->  /a                ->  ../../..
/a/b/c/d/e     ->  /a/b              ->  ../..
/a/b/c/d/e     ->  /a/b/CC           ->  ../../CC
/a/b/c/d/e     ->  /a/b/CC/DD/EE     ->  ../../CC/DD/EE
./a            ->  ./                ->  .
./a/b          ->  ./                ->  ..
./a/b          ->  ./a               ->  .
./a/b/c        ->  ./a               ->  ..
./a/b/c        ->  ./a/b             ->  .
./a/b/c        ->  ./a/b/CC          ->  CC
./a/b/c/d/e    ->  ./a               ->  ../../..
./a/b/c/d/e    ->  ./a/b/CC          ->  ../../CC
./a/b/c/d/e    ->  ./a/b/CC/DD/EE    ->  ../../CC/DD/EE
/a             ->  /x                ->  x
/a/b           ->  /x                ->  ../x
/a/b/c         ->  /x                ->  ../../x
/a             ->  /x/y              ->  x/y
/a/b           ->  /x/y              ->  ../x/y
/a/b/c         ->  /x/y              ->  ../../x/y
/x             ->  /a                ->  a
/x             ->  /a/b              ->  a/b
/x             ->  /a/b/c            ->  a/b/c
/x/y           ->  /a                ->  ../a
/x/y           ->  /a/b              ->  ../a/b
/x/y           ->  /a/b/c            ->  ../a/b/c
./a a/b/c/d/e  ->  ./a a/b/CC/DD/EE  ->  ../../CC/DD/EE
/x x/y y       ->  /a a/b b/c c      ->  ../a a/b b/c c

BTW, jika Anda ingin menggunakan skrip ini tanpa menyimpannya dan Anda mempercayai skrip ini, maka Anda memiliki dua cara untuk melakukannya dengan bash trick.

Impor relpathsebagai fungsi bash dengan mengikuti perintah. (Membutuhkan bash 4+)

source <(curl -sSL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath)

atau hubungi script on-the-fly seperti dengan curl bash combo.

curl -sSL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath | bash -s -- /a/b/c/d/e /a/b/CC/DD/EE
osexp2003
sumber