Nhận liên kết tương đối giữa hai đường dẫn


21

Nói rằng tôi có hai con đường: <source_path><target_path>. Tôi muốn shell của tôi (zsh) tự động tìm hiểu xem có cách nào để biểu diễn <target_path>từ <source_path>một đường dẫn tương đối không.

Ví dụ: giả sử

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

Kết quả sẽ là ../../hello/world

Tại sao tôi cần điều này:

Tôi cần thích để tạo ra một liên kết tượng trưng từ <source_path>để <target_path>sử dụng một họ hàng liên kết tượng trưng bất cứ khi nào có thể, vì nếu không thì máy chủ samba của chúng tôi không hiển thị các thuộc tính của file khi tôi truy cập vào các tập tin trên mạng từ Windows (tôi không phải là quản trị viên sys, và don' t có quyền kiểm soát cài đặt này)

Giả sử rằng <target_path><source_path>là các đường dẫn tuyệt đối, sau đây tạo ra một liên kết tượng trưng chỉ đến một đường dẫn tuyệt đối .

ln -s <target_path> <source_path>

vì vậy nó không hoạt động cho nhu cầu của tôi. Tôi cần phải làm điều này cho hàng trăm tệp, vì vậy tôi không thể tự sửa nó.

Bất kỳ vỏ được xây dựng mà chăm sóc này?


Bạn có thể sử dụng symlinksđể chuyển đổi các liên kết tuyệt đối đến tương đối.
Stéphane Chazelas

@StephaneChazelas thế nào? Bạn có thể gửi một câu trả lời giải thích?
terdon

Câu trả lời:


10

Bạn có thể sử dụng symlinkslệnh để chuyển đổi đường dẫn tuyệt đối sang tương đối:

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

Chúng tôi đã có các liên kết tuyệt đối, hãy chuyển đổi chúng thành tương đối:

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

Tài liệu tham khảo


Cảm ơn Stephane. Điều này giải quyết vấn đề gốc, vì vậy tôi đã chấp nhận. Điều đó nói rằng, thật tuyệt vời khi có một cái gì đó (ví dụ hàm zsh) có hai đường dẫn (nguồn và đích) và xuất đường dẫn tương đối từ nguồn tới đích (không có liên kết tượng trưng ở đây). Điều đó sẽ giải quyết câu hỏi trong tiêu đề là tốt.
Amelio Vazquez-Reina


24

Hãy thử sử dụng realpathlệnh (một phần của GNU coreutils; > = 8.23 ), ví dụ:

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

Nếu bạn đang sử dụng macOS, hãy cài đặt phiên bản GNU qua: brew install coreutilsvà sử dụng grealpath.

Lưu ý rằng cả hai đường dẫn cần tồn tại để lệnh thành công. Nếu bạn vẫn cần đường dẫn tương đối ngay cả khi một trong số chúng không tồn tại thì hãy thêm công tắc -m.

Để biết thêm ví dụ, xem Chuyển đổi đường dẫn tuyệt đối thành đường dẫn tương đối được cung cấp một thư mục hiện tại .


Tôi nhận được realpath: unrecognized option '--relative-to=.':( phiên bản realpath 1.19
phuclv

@ LưuVTHERPhúc Bạn cần sử dụng phiên bản GNU / Linux của realpath. Nếu bạn đang dùng macOS, hãy cài đặt qua : brew install coreutils.
kenorb

không, tôi đang dùng Ubuntu 14.04 LTS
phuclv

@ LưuViênPhúc Tôi đang ở realpath (GNU coreutils) 8.26nơi có tham số. Xem: gnu.org/software/coreutils/realpath Kiểm tra kỹ xem bạn có đang sử dụng bí danh không (ví dụ: chạy như \realpath) và cách bạn có thể cài đặt phiên bản từ coreutils.
kenorb


7

Tôi nghĩ rằng giải pháp python này (được lấy từ cùng một bài với câu trả lời của @ EvanTeitelman) cũng đáng được đề cập. Thêm phần này vào ~/.zshrc:

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

Sau đó, bạn có thể làm, ví dụ:

$ relpath /usr/local/share/doc/emacs /usr/local/share/fonts
../doc/emacs

@StephaneChazelas đây là bản sao chép trực tiếp từ câu trả lời được liên kết. Tôi là một anh chàng Perl và biết về cơ bản không có con trăn nên tôi không biết cách sử dụng sys.argv. Nếu mã được đăng là sai hoặc có thể được cải thiện, vui lòng làm như vậy với lời cảm ơn của tôi.
terdon

@StephaneChazelas Bây giờ tôi đã hiểu, cảm ơn vì đã chỉnh sửa.
terdon

7

Không có bất kỳ nội dung shell nào để giải quyết vấn đề này. Tôi đã tìm thấy một giải pháp trên Stack Overflow mặc dù. Tôi đã sao chép giải pháp ở đây với những sửa đổi nhỏ và đánh dấu câu trả lời này là wiki cộng đồng.

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

Bạn có thể sử dụng chức năng này như vậy:

source=/foo/bar/something
target=/foo/hello/world
ln -s "$(relpath "$source" "$target")" "$source"

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

Đây là giải pháp đơn giản và đẹp nhất cho vấn đề.

Ngoài ra, nó nên hoạt động trên tất cả các vỏ giống như bourne.

Và sự khôn ngoan ở đây là: hoàn toàn không cần đến các tiện ích bên ngoài hoặc thậm chí các điều kiện phức tạp. Một vài thay thế tiền tố / hậu tố và một vòng lặp while là tất cả những gì bạn cần để có được bất cứ thứ gì được thực hiện trên các vỏ giống như bourne.

Cũng có sẵn qua PasteBin: http://pastebin.com/CtTfvime


Cảm ơn giải pháp của bạn. Tôi thấy rằng để làm cho nó hoạt động trong một Makefiletôi cần thêm một cặp dấu ngoặc kép vào dòng pos=${pos%/*}(và, tất nhiên, dấu chấm phẩy và dấu gạch chéo ngược ở cuối mỗi dòng).
Cevanflexe

Ý tưởng không phải là xấu, nhưng trong phiên bản hiện tại rất nhiều trường hợp không làm việc: relpath /tmp /tmp, relpath /tmp /tmp/a, relpath /tmp/a /. Ngoài ra, bạn quên đề cập rằng tất cả các đường dẫn phải tuyệt đối VÀ được chuẩn hóa (nghĩa là /tmp/a/..không hoạt động)
Jérôme Pouiller

0

Tôi đã phải viết một tập lệnh bash để làm điều này một cách đáng tin cậy (và tất nhiên mà không cần xác minh sự tồn tại của tập tin).

Sử dụng

relpath <symlink path> <source path>

Trả về một đường dẫn nguồn tương đối.

thí dụ:

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

cả hai nhận được ../../CC/DD/EE


Để có một bài kiểm tra nhanh, bạn có thể chạy

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

kết quả: một danh sách kiểm tra

/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, nếu bạn muốn sử dụng tập lệnh này mà không lưu tập lệnh và bạn tin tưởng tập lệnh này, thì bạn có hai cách để thực hiện điều này với thủ thuật bash.

Nhập relpathdưới dạng hàm bash bằng lệnh sau. (Yêu cầu bash 4+)

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

hoặc gọi kịch bản nhanh chóng như với combo bash curl.

curl -sSL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath | bash -s -- /a/b/c/d/e /a/b/CC/DD/EE
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.