2つのパスがあるとします:<source_path>
および<target_path>
。シェル(zsh)に、<target_path>
からの<source_path>
を相対パスとして表す方法があるかどうかを自動的に検出してほしい。
例えば。仮定しましょう
<source_path>
は/foo/bar/something
です<target_path>
は/foo/hello/world
です結果は../../hello/world
になります
relativeシンボリックリンクを使用して、<source_path>
から<target_path>
へのシンボリックリンクを作成する必要があります。 Windowsからネットワーク上のファイル(私はsys管理者ではないため、この設定を制御できません)
<target_path>
と<source_path>
が絶対パスであるとすると、以下はシンボリックリンク絶対パスを指すを作成します。
ln -s <target_path> <source_path>
だから私のニーズには合いません。何百ものファイルに対してこれを行う必要があるので、手動で修正することはできません。
これを処理するシェルビルトインはありますか?
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/
realpath
コマンド ((GNU coreutils
の一部;> = 8.23)、例えば:
realpath --relative-to=/foo/bar/something /foo/hello/world
MacOSを使用している場合は、GNU version via:brew install coreutils
およびgrealpath
を使用します。
コマンドを正常に実行するには、両方のパスが存在する必要があることに注意してください。いずれかが存在しない場合でも相対パスが必要な場合は、-mスイッチを追加します。
その他の例については、「 絶対パスを現在のディレクトリの相対パスに変換 」を参照してください。
これを処理するShellビルトインはありません。私は 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"
この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
# 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ループの組み合わせは、ボーンのようなシェルでほぼ何でも実行するために必要なすべてです。
Pastebin経由でも利用可能: http://Pastebin.com/CtTfvime
これを確実に行うためには 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