realpath がシンボリックリンクを解決しないようにするにはどうすればよいですか?

realpath がシンボリックリンクを解決しないようにするにはどうすればよいですか?

シンボリックリンクを解決せずにファイルの絶対パスを返すコマンドを探しています。 一般的に、realpathこれはうまく機能します。

$ mkdir /tmp/test; cd /tmp/test
$ mkdir foo
$ ln -s foo bar
$ realpath -se bar           # good, this does not resolve the symlink
/tmp/test/bar

シンボリックリンク ディレクトリ内のファイルにも機能します。

$ touch foo/file
$ realpath -se bar/file      # good, this does not resolve the symlink
/tmp/test/bar/file

しかし、現在のディレクターがシンボリックリンクディレクトリ

$ cd bar
$ pwd
/tmp/test/bar
$ realpath -se file          # this fails, returning the target
/tmp/test/foo/file
$ realpath -se .             # this also fails, returning the target
/tmp/test/foo
$ realpath -se /tmp/test/bar/file # yet this works
/tmp/test/bar/file
$ realpath -se /tmp/test/bar # and this works
/tmp/test/bar

なぜrealpathそのように動作するのでしょうか? (バグでしょうか?)realpathシンボリックリンクを解決しないようにする方法はありますか、それとも別の方法を使用すべきでしょうか?

答え1

プロセスの現在の作業ディレクトリ (CWD) は、OS レベルで前のプロセスから継承されるか、 を使用して現在のプロセス用に変更することができますchdir(2)。OS (ここでは「カーネル」を意味します) は、もちろん、常にシンボリックリンクを解決して最終結果を決定しますが、最終結果はディレクトリであり、シンボリックリンク (ディレクトリへの) ではありません。たとえば、前のシステム コール ( chdir(2)) は、解決するシンボリック リンクが多すぎる場合にエラーを返すことができますELOOP。したがって、OS の観点からは、どのプロセスに対してもディレクトリではない CWD は存在できません。OS は常に、どこにもシンボリックリンクのない実際のパスとしてそれを解決します。

シェルが を実行するとcd /tmp/test/bar、CWD パスは OS によって に解決されます/tmp/test/foo。たとえば、Linux システムでは、ls -l /proc/$$/cwdカーネルが認識した解決されたパスへのリンクが表示されます/tmp/test/foo

シェルがbarプロンプトにまだ表示しているのは、CD以前に実行したコマンド。動作はシェルの種類によって異なります。ここでは bash を想定します。そのため、組み込みpwd(外部/bin/pwdコマンドではありません) の$PWD変数とその使用は、$PS1現在のディレクトリについてユーザーに「嘘」をつきます。

realpath、または/bin/pwdシェルから実行されるプロセスは、もちろん実際のCWD は です/tmp/test/foo。したがって、これは のバグではなくrealpath、 に関する特定の情報は決して得られませんbar

Kusalananda が提案しているように、1 つの厄介な方法は、何らかの方法で変数を再利用し$PWDrealpathその引数がまだ絶対値でない場合にのみ、それを の引数の先頭に追加することです。

ここに例があります。これを悪用する方法がないかどうかはわかりません。たとえば、以下の関数は対応できますが、変数$PWD自体は bash 4.4.12 (Debian 9) では適切に動作しませんが、パスに改行文字がある場合、bash 5.0.3 (Debian 10) では正常に動作します。どこかに改行文字がある場合、有用であるためにはオプション-zも追加する必要がありますrealpathが、この単純な例ではオプションの解析全体を再実装するつもりはありません。

myrealpathnofollowsym () {
    for p in "$@"; do
        if ! printf '%s' "$p" | grep -q -- '^/'; then
            realpath -se "$PWD/$p"
        else
            realpath -se "$p"
        fi
    done
}

関連情報