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


29

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

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

mypath=$(readlink -f $0)

...しかし、これは移植性がありません。(たとえば、readlinkダーウィンでは-fフラグを認識せず、同等の機能もありませんreadlink。)

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


回答:


28

zsh、それはただです:

mypath=$0:A

ただしrealpath()、他のシェルの場合、とreadlink()は標準関数(後者はシステムコール)でrealpathありreadlink、標準コマンドではありませんが、システムによっては、さまざまな動作と機能セットを持つ一方または両方があります。

頻繁に、移植性のために、あなたはに頼ることができます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が存在する限りファイルが存在しなくても文句を言わないという点で(GNU )readlink -fよりもGNUのように振る舞います。realpath()readlink -e


注:これはあなたのために動作しません.zshrc:代わりにこの投稿を参照してください。
ブライスギンタ

24

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

mypath=${0:a}

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

mydir=${0:a:h}

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


甘い!私は長年にわたってこのような何かを探していて、それを探しているzshマンページの全体を読んでいますが、「歴史の展開」の下を見るのは決して起こりませんでした。
VucarTimnärakrul14年

1
GNUの同等物readlink -fはむしろになります$0:A
ステファンシャゼル14年

11

私は数年前からこれを使用しています:

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

1
私はそれが好きです!これまでのところ、それはかなりポータブルです。Solarisの、OmniOS、クライアントは、Linux、Mac、Windows 2008上でも、Cygwinの上の作品
ティム・ケネディ

4
GNUと同等ではないのreadlink -fは、スクリプト自体がシンボリックリンクである場合です。
ステファンシャゼル14年

Ubuntu 16.04でzsh、これを直接またはサブシェル(推奨)で実行すると、ホームdir(/home/ville)で出力され/home/ville/zshます。
ヴィル

8

この構文は、(で試験したいずれのBourneシェルスタイルのインタプリタに移植する必要があり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シェル(非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

ありがとう。ただし、設定を解除$PWDするのはやり過ぎかもしれません-のように絶対電流に設定できますcd -P .。それがボーンシェルでうまくいくとは思いませんが、最初にテストしたすべてでうまくいくはずです。とにかく私のために。
mikeserv 14

@mooseどのOSを実行していますか?
jlliagre 14

ムースは誰ですか?何?
mikeserv 14

@mikeservムースは、といくつかの問題についてのコメントを掲載通行人あるzshdirnameHIR撤退迅速/彼女のコメント...しかし
jlliagre

Bourne Shellはcd(1)にgetopt()を使用しないため、Bourne ShellスクリプトはBourne Shellでは機能しません。
気味悪い

4

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

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

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

すべてのシンボリックリンクが解決されたパスを意味する場合、それは別の問題です。readlink -fLinux(一部の簡略化された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実行されなくなります。関数は、ディレクトリ名の場合は処理しません。)


1
出力が端末に送られないときに非印刷文字lsマングルする実装を知っていますか?
ステファンシャゼル14年

1
@StéphaneChazelas touch Stéphane; LC_ALL=C busybox ls Stéphane | catSt??phane(名前がUTF-8の場合、latin1は単一のを提供します?)。私は古い商用ユニックスでもそれを見たと思います。
ジル 'SO-悪であるのをやめる' 14年

@StéphaneChazelasいくつかのバグを修正しましたが、広範囲にテストしていません。場合によってはまだ失敗するかどうかを教えてください(一部のディレクトリで実行権限が不足していることを除き、そのような場合には対処しません)。
ジル 'SO-悪であるのをやめる' 14

@Gilles- busyboxこれは何ですか?gitに よるとbusybox ls、2011年以降、コードは変更されてbusybox lsいません。2013年頃- 私はこのことを行いません。この1 -年頃2012 - ありませんこれが理由を説明するかもしれません。busyboxUnicodeサポートを使用して構築しましたか?wcharサポートを含めますか?mkinitcpio busyboxパッケージのビルドオプションを確認するために、試してみることをお勧めします。
mikeserv 14

ジル-私は最初にこの答えを-または少なくともその一部を誤解したと思います。あなたのマングルファイル名のマントラは完全に誤解であると私は固く信じていますが、間違いなくあなたのpoor_mans_readlinkは非常によくできています。あなたが私に編集を行う親切さを与えてくれるなら-どんな編集もするだろう-そして後で私にpingを送り、私はこれに対する私の投票を取り消したいと思う。
mikeserv 14

1

必要なディレクトリへの絶対パスが必要な場合は、現在のディレクトリ(またはシェルスクリプトを実行したディレクトリ)に対する実行権限がある場合cd

cdの仕様のステップ10

場合は-Pオプションが有効になっている、$PWD環境変数はによって出力される文字列に設定されなければなりませんpwd -P。新しいディレクトリまたはそのディレクトリの親に現在の作業ディレクトリを決定するための十分な権限がない場合、$PWD環境変数の値は指定されません。

そして pwd -P

標準出力に書き込まれるパス名には、シンボリックリンクタイプのファイルを参照するコンポーネントは含まれません。pwdユーティリティが標準出力に書き込めるパス名が複数ある場合、1つは単一の/スラッシュ文字で始まり、1つ以上の/スラッシュ文字で始まる場合は、1つの/スラッシュ文字で始まるパス名を書き込みます。パス名には、先頭の1つまたは2つの/スラッシュ文字の後に不要な/スラッシュ文字を含めることはできません。

ので、それはだcd -Pものに、現在の作業ディレクトリを設定する必要がありpwd -Pそうでない場合は印刷する必要がありますし、そのcd -印刷している$OLDPWDこと、次の作品を:

mkdir ./dir
ln -s ./dir ./ln
cd ./ln ; cd . ; cd -

出力

/home/mikeserv/test/ln

それを待つ...

cd -P . ; cd . ; cd -

出力

/home/mikeserv/test/dir

そして、私が印刷しcd -ているときは、印刷してい$OLDPWDます 絶対パスになったらすぐにcd設定$PWDcd -P . $PWDます/-他の変数は必要ありません。そして実際に、私も末尾は必要ありません.が、リセットの指定された動作がある$PWD$HOMEするとき、対話型シェルで cd飾り気のないですが。ですから、開発するのは良い習慣です。

したがって、パスで上記を実行するだけでパス${0%/*}を検証する$0のに十分なはずです$0が、それ自体がソフトリンクである場合は、残念ながらディレクトリに変更することはできません。

これを処理する関数は次のとおりです。

zpath() { cd -P . || return
    _out() { printf "%s$_zdlm\n" "$PWD/${1##*/}"; }
    _cd()  { cd -P "$1" ; } >/dev/null 2>&1
    while [ $# -gt 0 ] && _cd .
    do  if     _cd "$1" 
        then   _out
        elif ! [ -L "$1" ] && [ -e "$1" ] 
        then   _cd "${1%/*}"; _out "$1"
        elif   [ -L "$1" ]
        then   ( while set -- "${1%?/}"; _cd "${1%/*}"; [ -L "${1##*/}" ]
                 do    set " $1" "$(_cd -; ls -nd -- "$1"; echo /)"
                       set -- "${2#*"$1" -> }"
                 done; _out "$1" 
    );  else   ( PS4=ERR:\ NO_SUCH_PATH; set -x; : "$1" )
    fi; _cd -; shift; done
    unset -f _out _cd; unset -v _zdlm                                    
}

サブシェルを呼び出さずに、現在のシェルでできる限りのことを行うように努めていますが、エラーを検出するサブシェルや、ディレクトリを指していないソフトリンクがあります。POSIX互換のシェルとPOSIX互換lsのクリーンな_function()名前空間に依存します。後者がなくてもunset正常に機能しますが、その場合、現在のシェル関数を上書きする場合があります。一般に、これらの依存関係はすべて、Unixマシンでかなり確実に使用できるはずです。

引数付きまたは引数なしで呼び出された場合、最初に行われるの$PWDはその標準値にリセットされます。必要に応じて、ターゲットへのリンクを解決します。引数なしで呼び出され、それはそれについてです。しかし、それらと一緒に呼び出されると、それぞれのパスを解決して正規化し、stderrそうでない場合はメッセージを出力します。

ほとんどの場合、現在のシェルで動作するため、任意の長さの引数リストを処理できるはずです。また、$_zdlm変数unsetそれが通過したときにも表示される)を探し、その各引数のすぐ右側にCエスケープされた値を出力します。各引数には、常に単一の\newline文字も続きます。

多くのディレクトリ変更を行いますが、正規の値に設定すること以外は、影響しませんが$PWD$OLDPWD通過した時点で決してカウントすることはできません。

可能な限り早く各引数を終了しようとします。それは最初にしようとcd$1ます。可能であれば、引数の正規のパスをに出力しますstdout。それができない場合、$1存在することを確認し、ソフトリンクではありません。trueの場合、印刷します。

このように$1して、ディレクトリを指し示していないシンボリックリンクでない限り、シェルがアドレス指定するファイルタイプ引数を処理します。その場合while、サブシェルでループを呼び出します。

lsリンクを読み取るために呼び出します。参照パスを確実に処理するために、最初に現在のディレクトリを初期値に変更する必要があります。そのため、コマンド置換サブシェルで関数は次のことを行います。

cd -...ls...echo /

lsリンクの名前と文字列を完全に含めるために必要な限り、出力の左側から削除します->。私は、最初のを避けるためにしようとしたもののでこれを行うshift$IFS、これは私が理解できるように近いとして最も信頼性の高い方法であるが判明しました。これは、Gillesのpoor_mans_readlinkと同じことであり、よくできています。

返されたファイル名lsが間違いなくソフトリンクでなくなるまで、このプロセスをループで繰り返します。その時点で、以前と同様にそのパスを正規化してcdから印刷します。

使用例:

zpath \
    /tmp/script \   #symlink to $HOME/test/dir/script.sh
    ln \            #symlink to ./dir/
    ln/nl \         #symlink to ../..
    /dev/fd/0 \     #currently a here-document like : dash <<\HD
    /dev/fd/1 \     #(zlink) | dash
    file \          #regular file                                             
    doesntexist \   #doesnt exist
    /dev/disk/by-path/pci-0000:00:16.2-usb-0:3:1.0-scsi-0:0:0:0 \
    /dev/./././././././null \
    . ..      

出力

/home/mikeserv/test/dir/script.sh
/home/mikeserv/test/dir/
/home/mikeserv/test/
/tmp/zshtpKRVx (deleted)
/proc/17420/fd/pipe:[1782312]
/home/mikeserv/test/file
ERR: NO_SUCH_PATH: doesntexist
/dev/sdd
/dev/null
/home/mikeserv/test/
/home/mikeserv/

または多分...

ls
dir/  file  file?  folder/  link@  ln@  script*  script3@  script4@

zdlm=\\0 zpath * | cat -A

出力

/home/mikeserv/test/dir/^@$               
/home/mikeserv/test/file^@$
/home/mikeserv/test/file$
^@$
/home/mikeserv/test/folder/^@$
/home/mikeserv/test/file$               #'link' -> 'file\n'
^@$
/home/mikeserv/test/dir/^@$             #'ln' -> './dir'
/home/mikeserv/test/script^@$
/home/mikeserv/test/dir/script.sh^@$    #'script3' -> './dir/script.sh'
/home/mikeserv/test/dir/script.sh^@$    #'script4' -> '/tmp/script' -> ...

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