注:これは、確実で移植可能な既製のソリューションであると私は信じています。そのため、常に長い時間がかかります。
以下は完全にPOSIX準拠のスクリプト/関数であり、したがってクロスプラットフォームです(macOS readlink
でも機能-f
し、10.12(Sierra)の時点ではまだサポートされていません)-POSIXシェル言語機能とPOSIX準拠のユーティリティ呼び出しのみを使用します。
これは、GNUの移植可能な実装ですreadlink -e
(より厳密なバージョンのreadlink -f
)。
あなたはできる実行スクリプトをしsh
たりソース機能をにbash
、ksh
とzsh
:
たとえば、スクリプト内で次のように使用して、実行中のスクリプトの真の元のディレクトリを取得し、シンボリックリンクを解決できます。
trueScriptDir=$(dirname -- "$(rreadlink "$0")")
rreadlink
スクリプト/関数定義:
コードは、この回答からの感謝の気持ちで適応されました。Node.jsがインストールされている場合は、でインストールできるスタンドアロンベースのユーティリティバージョンもここに
作成しました。bash
npm install rreadlink -g
#!/bin/sh
# SYNOPSIS
# rreadlink <fileOrDirPath>
# DESCRIPTION
# Resolves <fileOrDirPath> to its ultimate target, if it is a symlink, and
# prints its canonical path. If it is not a symlink, its own canonical path
# is printed.
# A broken symlink causes an error that reports the non-existent target.
# LIMITATIONS
# - Won't work with filenames with embedded newlines or filenames containing
# the string ' -> '.
# COMPATIBILITY
# This is a fully POSIX-compliant implementation of what GNU readlink's
# -e option does.
# EXAMPLE
# In a shell script, use the following to get that script's true directory of origin:
# trueScriptDir=$(dirname -- "$(rreadlink "$0")")
rreadlink() ( # Execute the function in a *subshell* to localize variables and the effect of `cd`.
target=$1 fname= targetDir= CDPATH=
# Try to make the execution environment as predictable as possible:
# All commands below are invoked via `command`, so we must make sure that
# `command` itself is not redefined as an alias or shell function.
# (Note that command is too inconsistent across shells, so we don't use it.)
# `command` is a *builtin* in bash, dash, ksh, zsh, and some platforms do not
# even have an external utility version of it (e.g, Ubuntu).
# `command` bypasses aliases and shell functions and also finds builtins
# in bash, dash, and ksh. In zsh, option POSIX_BUILTINS must be turned on for
# that to happen.
{ \unalias command; \unset -f command; } >/dev/null 2>&1
[ -n "$ZSH_VERSION" ] && options[POSIX_BUILTINS]=on # make zsh find *builtins* with `command` too.
while :; do # Resolve potential symlinks until the ultimate target is found.
[ -L "$target" ] || [ -e "$target" ] || { command printf '%s\n' "ERROR: '$target' does not exist." >&2; return 1; }
command cd "$(command dirname -- "$target")" # Change to target dir; necessary for correct resolution of target path.
fname=$(command basename -- "$target") # Extract filename.
[ "$fname" = '/' ] && fname='' # !! curiously, `basename /` returns '/'
if [ -L "$fname" ]; then
# Extract [next] target path, which may be defined
# *relative* to the symlink's own directory.
# Note: We parse `ls -l` output to find the symlink target
# which is the only POSIX-compliant, albeit somewhat fragile, way.
target=$(command ls -l "$fname")
target=${target#* -> }
continue # Resolve [next] symlink target.
fi
break # Ultimate target reached.
done
targetDir=$(command pwd -P) # Get canonical dir. path
# Output the ultimate target's canonical path.
# Note that we manually resolve paths ending in /. and /.. to make sure we have a normalized path.
if [ "$fname" = '.' ]; then
command printf '%s\n' "${targetDir%/}"
elif [ "$fname" = '..' ]; then
# Caveat: something like /var/.. will resolve to /private (assuming /var@ -> /private/var), i.e. the '..' is applied
# AFTER canonicalization.
command printf '%s\n' "$(command dirname -- "${targetDir}")"
else
command printf '%s\n' "${targetDir%/}/$fname"
fi
)
rreadlink "$@"
セキュリティの正接:
jarnoは、組み込みcommand
が同じ名前のエイリアスまたはシェル関数によってシャドウされないことを保証する関数に関して、コメントで尋ねます:
unalias
またはunset
、[
エイリアスまたはシェル関数として設定されている場合はどうなりますか?
本来の意味rreadlink
をcommand
持つようにすることの背後にある動機は、お気に入りのオプションを含めるように再定義するなど、対話型シェルで標準コマンドをシャドウするためによく使用される便利なエイリアスと関数をバイパスするために使用することls
です。
私はそれを気にせ、信頼できない、悪質な環境とそのあなたない限りしている取引を言っても安全だと思うunalias
か、unset
あるいは、そのことについては、 - while
、do
、... -再定義されて心配されていません。
あり、何かの機能がその本来の意味と動作を持つことに頼らなければならないことは-その周りに方法はありません。
POSIXのようなシェルは組み込みの再定義を可能にし、言語のキーワードでさえ本質的にセキュリティリスクです(そして、偏執的なコードを書くことは一般に困難です)。
特に懸念事項に対処するには:
関数は、元の意味に依存しunalias
、それunset
を持っています。それらの振る舞いを変えるような方法でそれらをシェル関数として再定義させるのは問題でしょう。コマンド名(など)を引用するとエイリアスがバイパスされるため、エイリアスとしての再定義は必ずしも問題ではありません。\unalias
しかし、引用はありませんシェルのオプションキーワード(while
、for
、if
、do
、...)とシェルキーワードは、シェルよりも優先行いながら機能に、bash
そしてzsh
あなたが実行する必要がありますシェル・キーワードの再定義を防ぐために、別名最高の優先順位を持っているunalias
とそれらの名前(非対話型 bash
シェル(スクリプトなど)では)エイリアスはデフォルトでは展開されません - shopt -s expand_aliases
が最初に明示的に呼び出された場合のみ)。
unalias
-組み込みとして-が元の意味を持つことを確認するには、最初\unset
にそれを使用する必要があります。これにはunset
、元の意味が必要です。
unset
は組み込みシェルなので、そのように呼び出されるようにするには、それ自体が関数として再定義されていないことを確認する必要があります。引用符でエイリアスフォームをバイパスすることはできますが、シェル関数フォーム-キャッチ22はバイパスできません。
したがって、unset
元の意味を信頼できる場合を除いて、私が言えることから、悪意のある再定義をすべて防ぐ方法は保証されていません。