Получение относительных связей между двумя путями

21

Скажем, у меня есть два пути: <source_path>и <target_path>. Я хотел бы, чтобы моя оболочка (zsh) автоматически выяснила, есть ли способ представить <target_path>из нее <source_path>относительный путь.

Например предположим

  • <source_path> является /foo/bar/something
  • <target_path> является /foo/hello/world

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

Зачем мне это нужно:

Мне нужно по возможности создавать символическую ссылку <source_path>на <target_path>использование относительной символической ссылки, поскольку в противном случае наш сервер samba не показывает файл должным образом, когда я получаю доступ к этим файлам в сети из Windows (я не являюсь системным администратором, и не не может контролировать эту настройку)

Предполагая, что <target_path>и <source_path>являются абсолютными путями, следующее создает символическую ссылку, указывающую на абсолютный путь .

ln -s <target_path> <source_path>

так что это не работает для моих нужд. Мне нужно сделать это для сотен файлов, поэтому я не могу просто исправить это вручную.

Есть встроенные оболочки, которые позаботятся об этом?

Амелио Васкес-Рейна
источник
Вы можете использовать symlinksдля преобразования абсолютных ссылок в относительные.
Стефан Шазелас
@StephaneChazelas как? Не могли бы вы опубликовать ответ с объяснением?
Terdon

Ответы:

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), которое принимает два пути (исходный и целевой) и выводит относительный путь от исходного к целевому (здесь нет символических ссылок). Это решило бы вопрос, заданный в названии.
Амелио Васкес-Рейна
@ user815423426, terdon ответил на эту часть
Стефан Шазелас
24

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

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

Если вы используете macOS, установите версию GNU через: brew install coreutilsи используйте grealpath.

Обратите внимание, что для успешной работы команды должны существовать оба пути. Если вам все равно нужен относительный путь, даже если один из них не существует, добавьте ключ -m.

Дополнительные примеры см. В разделе « Преобразование абсолютного пути в относительный путь для текущего каталога» .

kenorb
источник
Я получаю realpath: unrecognized option '--relative-to=.':( RealPath версия 1.19
Phuclv
@ LưuVĩnhPhúc Вам необходимо использовать версию GNU / Linux realpath. Если вы на MacOS, установить с помощью: brew install coreutils.
Кенорб
нет, я на Ubuntu 14.04 LTS
phuclv
@ LưuVĩnhPhúc Я нахожусь realpath (GNU coreutils) 8.26там, где присутствует параметр. См. Gnu.org/software/coreutils/realpath. Дважды проверьте, что вы не используете псевдоним (например, запустить от имени \realpath), и как вы можете установить версию из coreutils.
Кенорб
1
Кажется, что realpath в Ubuntu 14.04 действительно устарел , и единственный способ - перестроить coreutil вручную
phuclv
7

Я думаю, что это решение на python (взято из того же поста, что и ответ @ 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 парень и практически не знаю Python, поэтому я не знаю, как его использовать sys.argv. Если опубликованный код неверен или может быть улучшен, не стесняйтесь сделать это с моей благодарностью.
Terdon
@ StephaneChazelas Теперь я понимаю, спасибо за редактирование.
тердон
7

Нет никаких встроенных оболочек, чтобы позаботиться об этом. Я нашел решение по переполнению стека, хотя. Я скопировал здесь решение с небольшими изменениями и пометил этот ответ как вики сообщества.

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"
user26112
источник
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/}"
}

Это самое простое и красивое решение проблемы.

Также это должно работать на всех подобных борну оболочках.

И мудрость здесь такова: совершенно не нужны внешние утилиты или даже сложные условия. Пара замен префикса / суффикса и цикл while - это все, что вам нужно, чтобы получить практически все, что происходит в оболочках типа bourne.

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

Арто Пекканен
источник
Спасибо за ваше решение. Я обнаружил, что для того, чтобы это работало, Makefileмне нужно было добавить пару двойных кавычек в строку pos=${pos%/*}(и, конечно, точки с запятой и обратную косую черту в конце каждой строки).
Circonflexe
Идея не плохая , но в текущей версии много случаев не работает: relpath /tmp /tmp, relpath /tmp /tmp/a, relpath /tmp/a /. Кроме того, вы забыли упомянуть, что все пути должны быть абсолютными и /tmp/a/..
канонизированными
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

Кстати, если вы хотите использовать этот сценарий без сохранения и доверяете этому сценарию, то у вас есть два способа сделать это с помощью bash trick.

Импортируйте 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
osexp2003
источник