web-dev-qa-db-ja.com

スクリプトの絶対パスを取得するポータブルな方法?

(zsh)スクリプトがその絶対パスを決定するための移植可能な方法は何ですか?

Linuxでは私は次のようなものを使用します

mypath=$(readlink -f $0)

...しかし、これはポータブルではありません。 (たとえば、darwinのreadlink-fフラグを認識せず、同等のフラグもありません。)(また、これにreadlinkを使用することは、確かに、非常にあいまいなハックです。)

よりポータブルな方法は何ですか?

31
kjo

zshを使用すると、次のようになります。

_mypath=$0:A
_

他のシェルでは、realpath()readlink()は標準関数です(後者はシステムコールです)、realpathreadlinkは標準コマンドではありませんただし、一部のシステムには、さまざまな動作と機能セットを備えたどちらか一方または両方があります。

多くの場合、移植性のために、Perlに頼ることができます:

_abs_path() {
  Perl -MCwd -le '
    for (@ARGV) {
      if ($p = Cwd::abs_path $_) {
        print $p;
      } else {
        warn "abs_path: $_: $!\n";
        $ret = 1;
      }
    }
    exit $ret' "$@"
}
_

これは、dirnameが存在する限りファイルが存在しない場合でも文句を言わないという点で、realpath()(GNU _readlink -f_)よりもGNUの_readlink -e_のように動作します。

31

Zshでは、次のことができます。

mypath=${0:a}

または、スクリプトが存在するディレクトリを取得するには:

mydir=${0:a:h}

出典:zshexpn(1)manページ、セクションHISTORY EXPANSION、サブセクションModifiers(または単にinfo -f zsh -n Modifiers)。

27
mrmenken

私はこれを数年間使用しています:

# The absolute, canonical ( no ".." ) path to this script
canonical=$(cd -P -- "$(dirname -- "$0")" && printf '%s\n' "$(pwd -P)/$(basename -- "$0")")
11
user17591

この構文は、Bourne Shellスタイルのインタープリターに移植可能である必要があります(bashksh88ksh93zshmkshdashおよびbusybox sh):

mypath=$(exec 2>/dev/null;cd -- $(dirname "$0"); unset PWD; /usr/bin/pwd || /bin/pwd || pwd)
echo mypath=$mypath

このバージョンは、レガシーAT&T Bourne Shell(非POSIX)に互換性を追加します:

mypath=`dirname "$0"`
mypath=`exec 2>/dev/null;(cd -- "$mypath") && cd -- "$mypath"|| cd "$mypath"; unset PWD; /usr/bin/pwd || /bin/pwd || pwd`
echo mypath=$mypath
9
jlliagre

あなたが本当に絶対パス、つまりルートディレクトリからのパスを意味すると仮定します:

case $0 in
  /*) mypath=$0;;
  *) mypath=$PWD/$0;;
esac

ちなみに、これはどのBourneスタイルのシェルでも機能します。

すべてのシンボリックリンクが解決されたパスを意味する場合、それは別の問題です。 readlink -fは、Linux(一部の簡略化されたBusyBoxシステムを除く)、FreeBSD、NetBSD、OpenBSD、Cygwinでは機能しますが、OS/X、AIX、HP/UX、またはSolarisでは機能しません。 readlinkがある場合は、ループで呼び出すことができます。

realpath () {
  [ -e "$1" ] || return
  case $1 in
    /*) :;;
    *) set "$PWD/$1";;
  esac
  while [ -L "$1" ]; do
    set "${1%/*}" "$(readlink "$1")"
    case $2 in
      /*) set "$2";;
      *) if [ -z "$1" ]; then set "/$2"; else set "$(cd "$1" && pwd -P)/$2"; fi;;
    esac
  done
  case $1 in
    */.|*/..) set "$(cd "$1" && pwd -P)";;
    */./*|*/../*) set "$(cd "${1%/*}" && pwd -P)/${1##*/}"
  esac
  realpath=$1
}

readlinkがない場合は、ls -nで概算できますが、これは、lsがファイル名に印刷できない文字を変換しない場合にのみ機能します。

poor_mans_readlink () {
  if [ -L "$1" ]; then
    set -- "$1" "$(LC_ALL=C command ls -n -- "$2"; echo z)"
    set -- "${2%??}"
    set -- "${2#*"$1 -> "}"
  fi
  printf '%s\n' "$1"
}

(余分なzは、リンクターゲットが改行で終わっている場合に使用します。そうしないと、コマンドの置換によって食い尽くされてしまいます。ところで、realpath関数は、ディレクトリ名の大文字小文字を処理しません。 )

アルゴリズムの再定義を防ぐためにpythonが利用可能な場合、交感神経のワンライナーはどうですか?

function readlink { python -c "import os.path; print os.path.realpath('$1')"; }

https://stackoverflow.com/a/7305217 と同じ