2つのパス間の相対リンクを取得する


21

次の2つのパスがある<source_path>とし<target_path>ます。<target_path>from <source_path>を相対パスとして表現する方法があるかどうか、シェル(zsh)が自動的に検出するようにします。

例えば、仮定しましょう

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

結果は ../../hello/world

これが必要な理由:

私はからのシンボリックリンクを作成したいとする必要<source_path><target_path>使用して相対的な「私はsysの管理者ではないよ(私は、Windowsからネットワーク上のこれらのファイルにアクセスするとそうでない場合は、当社のSambaサーバがファイルを適切に表示されていないので、シンボリックリンク可能な限りの、そしてドンこの設定を制御できます)

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

参照資料


ありがとうステファン。これは根本的な問題を解決するので、受け入れました。とはいえ、2つのパス(ソースとターゲット)を取り、ソースからターゲットへの相対パスを出力するもの(たとえば、シンボリックリンクなし)があれば素晴らしいでしょう(たとえば、zsh関数)。それはタイトルで尋ねられた質問も解決します。
アメリオバスケスレイナ


24

realpathコマンド(GNUの一部coreutils; > = 8.23)を使用してみてください。例:

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

macOSを使用している場合は、次brew install coreutilsを使用してGNUバージョンをインストールし、を使用しますgrealpath

コマンドを正常に実行するには、両方のパスが存在する必要があることに注意してください。いずれかが存在しなくても相対パスが必要な場合は、-mスイッチを追加します。

その他の例については、現在のディレクトリを指定して絶対パスを相対パスに変換するを参照してください。


取得realpath: unrecognized option '--relative-to=.':( realpathバージョン1.19
phuclv

@LưuVĩnhPhúcGNU / 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
ケノーブ


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。投稿されたコードが間違っているか、改善できる場合は、お気軽に感謝します。
テルドン

@StephaneChazelas編集、ありがとうございます。
テルドン

7

これを処理するシェル組み込み関数はありません。しかし、Stack Overflowで解決策を見つけまし。ここにソリューションを少し修正してコピーし、この回答をコミュニティ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"
}

この関数は次のように使用できます。

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

これは、問題に対する最も単純で最も美しい解決策です。

また、すべてのボーンのようなシェルで動作するはずです。

そして、ここでの知恵は次のとおりです。外部ユーティリティや複雑な条件さえまったく必要ありません。いくつかのプレフィックス/サフィックスの置換とwhileループがあれば、bourneのようなシェルで何でもできるようになります。

PasteBinからも入手可能:http ://pastebin.com/CtTfvime


ソリューションをありがとう。で動作させるMakefileには、行に二重引用符を追加する必要があることがわかりましたpos=${pos%/*}(そして、もちろん、すべての行の終わりにセミコロンとバックスラッシュがあります)。
Circonflexe

:アイデアは、例現在のバージョン豊富に動作しない、悪くないですがrelpath /tmp /tmprelpath /tmp /tmp/arelpath /tmp/a /。アドオンでは、あなたは(つまり、すべてのパスが絶対的および正規化しなければならないことを言及するのを忘れ/tmp/a/..ないで作業を行う)
ジェローム・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

ところで、このスクリプトを保存せずに使用し、このスクリプトを信頼する場合は、bashトリックを使用してこれを行う2つの方法があります。

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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.