241

(Unix ライクなシステムで) 絶対パスまたは相対パスを指定すると、中間のシンボリック リンクを解決した後に、ターゲットのフル パスを特定したいと考えています。~username 表記も同時に解決できるボーナスポイント。

ターゲットがディレクトリの場合、そのディレクトリに chdir() してから getcwd() を呼び出すこともできるかもしれませんが、C ヘルパーを作成するのではなく、シェル スクリプトからこれを実行したいと考えています。残念ながら、シェルはユーザーからシンボリックリンクの存在を隠そうとする傾向があります (これは OS X の bash です):

$ ls -ld foo bar
drwxr-xr-x   2 greg  greg  68 Aug 11 22:36 bar
lrwxr-xr-x   1 greg  greg   3 Aug 11 22:36 foo -> bar
$ cd foo
$ pwd
/Users/greg/tmp/foo
$

私が欲しいのは、上記の例の tmp ディレクトリから実行すると、resolve("foo") == "/Users/greg/tmp/bar" となるような関数 resolve() です。

4

19 に答える 19

436
readlink -f "$path"

編集者注: 上記はGNU readlinkおよびFreeBSD/PC-BSD/OpenBSD readlinkで動作しますが、10.11 の OS X では動作しません。
GNU は、最終的なターゲットが存在するかどうかに関係なく、シンボリック リンクを解決するためのreadlink追加の関連オプションを提供します。-m

GNU coreutils 8.15 (2012-01-06) 以降、上記よりも鈍くなく柔軟なrealpathプログラムが利用可能になっていることに注意してください。同名の FreeBSD ユーティリティとも互換性があります。また、2 つのファイル間の相対パスを生成する機能も含まれています。

realpath $path

[halloleo のコメントから管理者が追加 <a href="/users/65889/danorton">danorton]

Mac OS X (少なくとも 10.11.x まで)の場合、オプションreadlinkなしで使用します。-f

readlink $path

編集者注: これはシンボリック リンクを再帰的に解決しないため、最終的なターゲットを報告しません。aたとえば、を指すシンボリック リンクが指定されb、それが を指すc場合、これは報告するだけです(絶対パスbとして出力されることは保証されません)。 OS Xで次のコマンドを使用して、不足している機能のギャップを埋めます。
perlreadlink -f
perl -MCwd -le 'print Cwd::abs_path(shift)' "$path"

于 2008-09-04T00:33:45.160 に答える
99

標準によれば、pwd -Pシンボリックリンクが解決されたパスを返す必要があります。

C 関数char *getcwd(char *buf, size_t size)fromunistd.hは同じ動作をする必要があります。

getcwd pwd

于 2008-08-11T10:48:19.260 に答える
28

「pwd -P」は、ディレクトリだけが必要な場合は機能するようですが、何らかの理由で実際の実行可能ファイルの名前が必要な場合は、役に立たないと思います。これが私の解決策です:

#!/bin/bash

# get the absolute path of the executable
SELF_PATH=$(cd -P -- "$(dirname -- "$0")" && pwd -P) && SELF_PATH=$SELF_PATH/$(basename -- "$0")

# resolve symlinks
while [[ -h $SELF_PATH ]]; do
    # 1) cd to directory of the symlink
    # 2) cd to the directory of where the symlink points
    # 3) get the pwd
    # 4) append the basename
    DIR=$(dirname -- "$SELF_PATH")
    SYM=$(readlink "$SELF_PATH")
    SELF_PATH=$(cd "$DIR" && cd "$(dirname -- "$SYM")" && pwd)/$(basename -- "$SYM")
done
于 2009-03-30T14:54:59.073 に答える
22

私のお気に入りの一つはrealpath foo

realpath - 正規化された絶対パス名を返す

realpath はすべてのシンボリック リンクを展開し、パスによって指定されたヌル終了文字列内の「/./」、「/../」、および余分な「/」文字への参照を解決し、
       正規化された絶対パス名を、resolved_pa​​th で指定されたサイズ PATH_MAX のバッファーに格納します。結果のパスには、シンボリック リンク「/./」または
       「/../」コンポーネント。
于 2008-12-04T23:39:05.553 に答える
10
readlink -e [filepath]

まさにあなたが求めているもののようです-任意のパスを受け入れ、すべてのシンボリックリンクを解決し、「実際の」パスを返します-そして、すべてのシステムがすでに持っている可能性が高いのは「標準の* nix」です

于 2015-07-09T14:34:38.377 に答える
6

別の方法:

# Gets the real path of a link, following all links
myreadlink() { [ ! -h "$1" ] && echo "$1" || (local link="$(expr "$(command ls -ld -- "$1")" : '.*-> \(.*\)$')"; cd $(dirname $1); myreadlink "$link" | sed "s|^\([^/].*\)\$|$(dirname $1)/\1|"); }

# Returns the absolute path to a command, maybe in $PATH (which) or not. If not found, returns the same
whereis() { echo $1 | sed "s|^\([^/].*/.*\)|$(pwd)/\1|;s|^\([^/]*\)$|$(which -- $1)|;s|^$|$1|"; } 

# Returns the realpath of a called command.
whereis_realpath() { local SCRIPT_PATH=$(whereis $1); myreadlink ${SCRIPT_PATH} | sed "s|^\([^/].*\)\$|$(dirname ${SCRIPT_PATH})/\1|"; } 
于 2011-09-13T10:52:44.777 に答える
5

readlink はほとんどのシステムで利用可能ですが、異なる引数が必要であることを知って、与えられたソリューションのいくつかをまとめると、これは OSX と Debian でうまく機能します。BSD システムについてはよくわかりません。OSX のみ[[ $OSTYPE != darwin* ]]から除外する必要があるかもしれません。-f

#!/bin/bash
MY_DIR=$( cd $(dirname $(readlink `[[ $OSTYPE == linux* ]] && echo "-f"` $0)) ; pwd -P)
echo "$MY_DIR"
于 2016-11-14T07:47:17.713 に答える
4

インライン Perl スクリプトを使用して、MacOS/Unix でファイルへの実際のパスを取得する方法は次のとおりです。

FILE=$(perl -e "use Cwd qw(abs_path); print abs_path('$0')")

同様に、シンボリックリンクされたファイルのディレクトリを取得するには:

DIR=$(perl -e "use Cwd qw(abs_path); use File::Basename; print dirname(abs_path('$0'))")
于 2018-01-30T03:57:48.270 に答える
3

一般的なシェル スクリプトは、シンボリック リンクとして呼び出された場合でも、「ホーム」ディレクトリを見つける必要があることがよくあります。したがって、スクリプトは $0 から「実際の」位置を見つける必要があります。

cat `mvn`

私のシステムでは、以下を含むスクリプトが出力されます。これは、必要なものの良いヒントになるはずです。

if [ -z "$M2_HOME" ] ; then
  ## resolve links - $0 may be a link to maven's home
  PRG="$0"

  # need this for relative symlinks
  while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
      PRG="$link"
    else
      PRG="`dirname "$PRG"`/$link"
    fi
  done

  saveddir=`pwd`

  M2_HOME=`dirname "$PRG"`/..

  # make it fully qualified
  M2_HOME=`cd "$M2_HOME" && pwd`
于 2008-08-11T11:17:23.803 に答える
2

注: これは堅固で移植可能な既製のソリューションであると信じていますが、そのために常に時間がかかります。

以下は、完全に POSIX に準拠したスクリプト/関数であり、したがってクロスプラットフォームです( 10.12 (Sierra) の時点でreadlinkはまだサポートされていないmacOS でも動作します) - POSIX シェル言語機能と POSIX 準拠のユーティリティ呼び出しのみを使用します。 .-f

これは、GNUreadlink -e (のより厳密なバージョンreadlink -f) の移植可能な実装です。

、およびでスクリプト実行するshか、関数ソースとしてbashkshzsh実行できます。

たとえば、スクリプト内で次のように使用して、実行中のスクリプトの元の真のディレクトリを取得し、シンボリックリンクを解決できます。

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ことは問題ではないと言って差し支えありません。whiledo

関数が本来の意味と動作を持つために依存しなければならないものがあります - それを回避する方法はありません。
POSIX ライクなシェルではビルトインの再定義が可能であり、言語キーワードでさえも、本質的にセキュリティ リスクです (そして偏執的なコードを書くことは一般的に困難です)。

あなたの懸念に具体的に対処するには:

関数は、元の意味を持つことにunalias依存しています。それらの動作を変更する方法でシェル関数unsetとして再定義することは問題になります。コマンド名 (の一部)を引用すると (例: ) エイリアスが回避されるため、エイリアスとしての再定義は必ずしも問題ではありません。\unalias

ただし、引用符はシェルキーワード( 、、、、 ...)のオプションではありません。また、シェル キーワードはシェル関数よりも優先されますが、 inおよびエイリアスは最も優先度が高いため、シェル キーワードの再定義を防ぐには、次のコマンドを使用して実行する必要があります。それらの名前 (ただし、非対話型シェル (スクリプトなど) では、エイリアスはデフォルトでは展開されません-が最初に明示的に呼び出された場合のみ)。whileforifdobashzshunalias bashshopt -s expand_aliases

unaliasビルトインとして、元の意味を持つようにするには、最初に on を使用する必要があります。\unsetこれにunsetは、元の意味を持つ必要があります。

unsetはシェルの組み込みであるため、そのように呼び出されるようにするには、それ自体が関数として再定義されていないことを確認する必要があります。引用符でエイリアス形式をバイパスすることはできますが、シェル関数形式 (catch 22) をバイパスすることはできません。

したがって、私が知る限り、その元の意味を信頼できない限り、unsetすべての悪意のある再定義から防御する保証された方法はありません。

于 2015-10-21T18:39:18.077 に答える
2
function realpath {
    local r=$1; local t=$(readlink $r)
    while [ $t ]; do
        r=$(cd $(dirname $r) && cd $(dirname $t) && pwd -P)/$(basename $t)
        t=$(readlink $r)
    done
    echo $r
}

#example usage
SCRIPT_PARENT_DIR=$(dirname $(realpath "$0"))/..
于 2014-04-10T14:52:57.620 に答える
2

これは、リンクがディレクトリであるか非ディレクトリであるかに関係なく機能する Bash のシンボリック リンク リゾルバーです。

function readlinks {(
  set -o errexit -o nounset
  declare n=0 limit=1024 link="$1"

  # If it's a directory, just skip all this.
  if cd "$link" 2>/dev/null
  then
    pwd -P
    return 0
  fi

  # Resolve until we are out of links (or recurse too deep).
  while [[ -L $link ]] && [[ $n -lt $limit ]]
  do
    cd "$(dirname -- "$link")"
    n=$((n + 1))
    link="$(readlink -- "${link##*/}")"
  done
  cd "$(dirname -- "$link")"

  if [[ $n -ge $limit ]]
  then
    echo "Recursion limit ($limit) exceeded." >&2
    return 2
  fi

  printf '%s/%s\n' "$(pwd -P)" "${link##*/}"
)}

cdすべてがsetサブシェルで行われることに注意してください。

于 2017-08-23T00:49:39.200 に答える
1

私は何年にもわたって何度もこれに出くわし、今回はOSXとLinuxで使用できる純粋なbashポータブルバージョンが必要だったので、先に進んで1つ書きました:

生きているバージョンはここにあります:

https://github.com/keen99/shell-functions/tree/master/resolve_path

しかし、SOのために、これが現在のバージョンです(十分にテストされていると思います..しかし、フィードバックをお待ちしています!)

普通の bourne シェル (sh) で動作させるのは難しくないかもしれませんが、私は試しませんでした...私は $FUNCNAME が好きすぎます。:)

#!/bin/bash

resolve_path() {
    #I'm bash only, please!
    # usage:  resolve_path <a file or directory> 
    # follows symlinks and relative paths, returns a full real path
    #
    local owd="$PWD"
    #echo "$FUNCNAME for $1" >&2
    local opath="$1"
    local npath=""
    local obase=$(basename "$opath")
    local odir=$(dirname "$opath")
    if [[ -L "$opath" ]]
    then
    #it's a link.
    #file or directory, we want to cd into it's dir
        cd $odir
    #then extract where the link points.
        npath=$(readlink "$obase")
        #have to -L BEFORE we -f, because -f includes -L :(
        if [[ -L $npath ]]
         then
        #the link points to another symlink, so go follow that.
            resolve_path "$npath"
            #and finish out early, we're done.
            return $?
            #done
        elif [[ -f $npath ]]
        #the link points to a file.
         then
            #get the dir for the new file
            nbase=$(basename $npath)
            npath=$(dirname $npath)
            cd "$npath"
            ndir=$(pwd -P)
            retval=0
            #done
        elif [[ -d $npath ]]
         then
        #the link points to a directory.
            cd "$npath"
            ndir=$(pwd -P)
            retval=0
            #done
        else
            echo "$FUNCNAME: ERROR: unknown condition inside link!!" >&2
            echo "opath [[ $opath ]]" >&2
            echo "npath [[ $npath ]]" >&2
            return 1
        fi
    else
        if ! [[ -e "$opath" ]]
         then
            echo "$FUNCNAME: $opath: No such file or directory" >&2
            return 1
            #and break early
        elif [[ -d "$opath" ]]
         then 
            cd "$opath"
            ndir=$(pwd -P)
            retval=0
            #done
        elif [[ -f "$opath" ]]
         then
            cd $odir
            ndir=$(pwd -P)
            nbase=$(basename "$opath")
            retval=0
            #done
        else
            echo "$FUNCNAME: ERROR: unknown condition outside link!!" >&2
            echo "opath [[ $opath ]]" >&2
            return 1
        fi
    fi
    #now assemble our output
    echo -n "$ndir"
    if [[ "x${nbase:=}" != "x" ]]
     then
        echo "/$nbase"
    else 
        echo
    fi
    #now return to where we were
    cd "$owd"
    return $retval
}

これはbrewのおかげで古典的な例です:

%% ls -l `which mvn`
lrwxr-xr-x  1 draistrick  502  29 Dec 17 10:50 /usr/local/bin/mvn@ -> ../Cellar/maven/3.2.3/bin/mvn

この関数を使用すると、-real- パスが返されます。

%% cat test.sh
#!/bin/bash
. resolve_path.inc
echo
echo "relative symlinked path:"
which mvn
echo
echo "and the real path:"
resolve_path `which mvn`


%% test.sh

relative symlinked path:
/usr/local/bin/mvn

and the real path:
/usr/local/Cellar/maven/3.2.3/libexec/bin/mvn 
于 2014-12-24T21:06:54.627 に答える
1

Macの非互換性を回避するために、私は思いつきました

echo `php -r "echo realpath('foo');"`

良くないがクロス OS

于 2014-08-08T08:50:18.657 に答える