Отримання відносних зв’язків між двома шляхами


21

Скажіть, у мене два шляхи: <source_path>і <target_path>. Я хотів би мою оболонку (ЗШ) , щоб автоматично дізнатися, якщо є спосіб представлення <target_path>від <source_path>як відносний шлях.

Наприклад, припустимо

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

Результат був би ../../hello/world

Навіщо мені це потрібно:

Я повинен був би створити символічне посилання з <source_path>для <target_path>використання відносної символічного посилання , де це можливо, так як в противному випадку наш самба сервер не показує файл належним чином, коли я отримати доступ до цих файлів по мережі з ОС Windows (я не системний адміністратор, а не " не мати контроль над цим налаштуванням)

Припускаючи, що це <target_path>і <source_path>є абсолютними шляхами, наступне створює символічне посилання, що вказує на абсолютний шлях .

ln -s <target_path> <source_path>

тому це не працює для моїх потреб. Мені потрібно це зробити для сотень файлів, тому я не можу просто виправити це вручну.

Будь-які вбудовані оболонки, які опікуються цим?


Ви можете використовувати symlinksдля перетворення абсолютних посилань у відносні.
Стефан Шазелас

@StephaneChazelas як? Чи можете ви опублікувати відповідь, що пояснює?
тердон

Відповіді:


10

Ви можете використовувати symlinksкоманду для перетворення абсолютних шляхів у відносні:

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

У нас є абсолютні посилання, давайте перетворимо їх на відносні:

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

Список літератури


Дякую Стефане. Це вирішує корінь проблеми, тому я прийняв. Однак, було б чудово мати щось (наприклад, zsh-функція), яке займає два шляхи (джерело та ціль) та виводить відносний шлях від джерела до цілі (тут немає символічних посилань). Це також вирішило б питання, задане в назві.
Амеліо Васкес-Рейна


24

Спробуйте скористатися realpathкомандою (частина GNU coreutils; > = 8.23 ), наприклад:

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

Якщо ви використовуєте macOS, встановіть версію GNU за допомогою: brew install coreutils та використовуйте grealpath.

Зауважте, що для успішної роботи команди повинні існувати обидва шляхи. Якщо вам взагалі потрібен відносний шлях, навіть якщо одного з них не існує, додайте перемикач -m.

Щоб отримати додаткові приклади, див. Перетворення абсолютного шляху у відносний шлях із заданим поточним каталогом .


Я отримую realpath: unrecognized option '--relative-to=.':( realpath версія 1.19
phuclv

@ LưuVĩnhPhúc Вам потрібно використовувати версію GNU / Linux realpath. Якщо ви на MacOS, встановити з допомогою: brew install coreutils.
kenorb

ні, я на Ubuntu 14.04 LTS
phuclv

@ LưuVĩnhPhúc Я перебуваю realpath (GNU coreutils) 8.26там, де параметр присутній. Дивіться: gnu.org/software/coreutils/realpath Двічі переконайтесь, що ви не використовуєте псевдонім (наприклад, запустити як \realpath) і як ви можете встановити версію з coreutils.
kenorb


7

Я думаю, що це рішення пітона (взято з тієї ж посади, що й відповідь @ EvanTeitelman) також варто згадати. Додайте це до свого ~/.zshrc:

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

Потім ви можете зробити, наприклад:

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

@StephaneChazelas це була пряма копія вставки з пов'язаної відповіді. Я хлопець Perl і по суті не знаю пітона, тому не знаю, як його використовувати sys.argv. Якщо розміщений код неправильний або його можна вдосконалити, не соромтеся зробити це з вдячністю.
terdon

@StephaneChazelas Я розумію зараз, дякую за редагування.
тердон

7

Немає жодної вбудованої оболонки для того, щоб подбати про це. Однак я знайшов рішення щодо Stack Overflow . Я скопіював рішення тут з невеликими модифікаціями та позначив цю відповідь як вікі спільноти.

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

Ви можете використовувати цю функцію так:

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

Це найпростіше і найкрасивіше рішення проблеми.

Також він повинен працювати на всіх бурштинових оболонках.

І мудрість тут полягає в тому, що зовнішніх комунальних служб або навіть складних умов абсолютно немає потреби. Пара заміни префікса / суфікса та цикл час - це все, що вам потрібно зробити майже про що-небудь, що робиться на оболонках, схожих на Bourne.

Також доступний через PasteBin: http://pastebin.com/CtTfvime


Дякуємо за ваше рішення. Я виявив, що для того, щоб це працювало в Makefileлінійці, мені потрібно було додати рядку подвійних лапок до рядка pos=${pos%/*}(і, звичайно, крапки з комою та зворотні риски в кінці кожного рядка).
Circonflexe

Ідея не погана , але в поточній версії багато випадків не працює: relpath /tmp /tmp, relpath /tmp /tmp/a, relpath /tmp/a /. Крім того, ви забудете згадати, що всі шляхи повинні бути абсолютними І канонізованими (тобто /tmp/a/..не працює)
Jérôme Pouiller

0

Мені довелося написати сценарій bash, щоб це зробити надійно (і, звичайно, не перевіряючи наявність файлу).

Використання

relpath <symlink path> <source path>

Повертає відносний шлях до джерела.

приклад:

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

обидва отримують ../../CC/DD/EE


Для швидкого тестування можна запустити

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

результат: список тесту

/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, якщо ви хочете використовувати цей скрипт, не зберігаючи його, і ви довіряєте цьому сценарію, то у вас є два шляхи до цього за допомогою баш-трюку.

Імпортуйте relpathяк функцію bash, виконавши команду. (Потрібна версія bash 4+)

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

або зателефонуйте в сценарій на ходу, як у поєднанні з curl bash комбо.

curl -sSL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath | bash -s -- /a/b/c/d/e /a/b/CC/DD/EE
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.