はじめに
readlink
コマンドと realpath
コマンドは、どちらもシンボリックリンクのリンク先を調べるコマンドです。readlink -f
は realpath
とほぼ同等です。
-
readlink
はシンボリックリンクファイルのリンク先を調べるコマンド -
realpath
はシンボリックを再帰的に解決し実際のパスを調べるコマンド
readlink
コマンドと realpath
コマンドは多くの環境で使えるようになり、POSIX.1-2024 (Issue 8) で標準化されました。標準化は一般的に実装された後に行われます。ほとんどの環境ですでに実装ずみであるため、使ったことがある人は多いでしょう。もし古い使えない環境にも対応したい場合には、readlink -f
と互換性がある readlinkf
関数を作っているので環境が対応するまでそれを使用して時間稼ぎができます。
readlink
コマンドと realpath
コマンドを使ったことがある人は、どちらも同じような機能を持っているコマンドだと考えているのではないかと思います。この二つのコマンドは POSIX で標準化されたときに役割が明確に分離されました。この記事では readlink
コマンドと realpath
コマンドの現状と歴史について解説します。
どこでも使えるようになった readlink -f と realpath
まず、この記事にたどり着いた人がおそらく気にしていることだと思う件について、簡単に説明しておきます。シンボリックリンクを(再帰的に)解決して、ファイルの実体を取得する方法には、readlink
コマンドを使う方法と、 realpath
コマンドを使う方法の二つがあります。 readlink
コマンドはデフォルトではシンボリックリンクが示すリンク先を取得するだけなので、リンク先がシンボリックリンクの場合にそのまま出力されます。リンク先を再帰的たどるって最終的なパスを取得するにはには -f
オプションを指定します。
readlink
コマンド Linux や BSD 系 OS の多くの環境でインストールされていたコマンドですが、-f
オプションは長い間使えませんでした。BSD 系 Unix では -f
オプションは 2010 年代の中頃に使えるようになり、最後に 2022 年の macOS 12.3 から使えるようになりました。現在ではほとんどの環境でも使えるオプションですが、 readlink
コマンドの -f
オプションは POSIX では標準化されていないので注意してください。
realpath
コマンドもまたほとんどの環境でも使えるコマンドになりましたが、readlink
コマンドよりも普及が遅く、歴史的にみると使える環境が少なという問題がありました。それでも徐々に広まっていき、macOS では 2022 年の 13.0 から使えるようになりました。現在では readlink -f
も realpath
もほとんどの環境で使うことができるようになりました。
POSIX で標準化された readlink
と realpath
POSIX で標準化された readlink
と realpath
はオプションが少なく使い方も簡単です。
readlink [-n] file
-n: 末尾の改行を出力しない
realpath [-e | -E] file
-e: ファイルが見つからない場合にエラーにする
-E: ファイルが見つからない場合にエラーにしない
POSIX 標準化された範囲では readlink
は再帰的にシンボリックリンクのパスを解決するものではなく「シンボリックリンクファイルのリンク先を取得するだけ」のコマンドです。再帰的にパスを解決するのではなく単純にシンボリックリンクファイルの中身(リンク先)を取得するだけです。名前と機能がうまくマッチしていると思います。
実際の実装はもっと多くのオプションを持ち、readlink
と realpath
は同じように使うことができたため、使い分けに混乱していたのですが、POSIX の標準化の内容ではそれがなくなりました。私は最初、POSIX はなぜ似たようなコマンドを二つとも標準化したのか疑問だったのですが、よく見ると役割を明確に分けたということがわかります。
readlink
コマンドと realpath
コマンドの歴史
readlink
と realpath
はどちらも古くからあるコマンドです。どちらもシステムコールまたはC言語関数の readlink()
と realpath()
をラップしたものとして何度も再発明されたようで、おそらく以下の説明以外の実装もあると思われます。
realpath
は Debian に含まれている dwww パッケージの一部として 1996 年頃には存在していたようです。realpath
コマンドを dwww なしで単独で使いたいと要望があり、2002 年の Debian 3.0 から realpath パッケージとして独立したようです。これが Linux 系で昔に使われていた初期の realpath
コマンドです。2001 年には FreeBSD 4.3 でも実装されており、リリースのタイミングを考慮すればほぼ同時に FreeBSD と Debain で realpath
コマンドが使えるようになったようです。2012 年には GNU 版が実装され、Red Hat 系では 2014 年の RHEL 7.0 から使えるようになり、Debian では 2015 年の Debian 8 から GNU 版に置き換えられました。2022 年以降 BSD 系 Unix でも実装されましたが、おそらく POSIX による標準化をきっかけに先行実装したようです。
readlink
コマンドは 1997 年の OpenBSD 2.1 で Perl 組み込みの readlink
関数の置き換えとして実装されたのが最初です。そしてすぐに -f
オプションが追加されました。続いて readlink
コマンドは 2002 年に NetBSD が実装しました。続いて GNU、FreeBSD で実装されました。readlink
コマンドは GNU が実装した Linux にしか無いコマンドだと勘違いされがちがですが、実際には readlink
コマンドは Linux よりも先に BSD 系 Unix で実装されていたことがわかります。大まかな時系列は次のとおりです。
realpath | readlink | |
---|---|---|
1996 年 | dwww パッケージの一部 | |
1997 年 | OpenBSD 2.1, OpenBSD 2.2 (-f) | |
2001 年 | FreeBSD 4.3 | |
2002 年 | Debian 3.0(dwwwから独立) | NetBSD 1.6 |
2003 年 | GNU Coreutils 4.5.5 (-f) | |
2004 年 | FreeBSD 4.10 | |
2007 年 | NetBSD 4.0 (-f) | |
2012 年 | GNU Coreutils 8.15 | FreeBSD 8.3 (-f), Mac OS X 10.8 |
2014 年 | RHEL 7.0(GNU版を組み込み) | |
2015 年 | Debian 8(GNU版に変更) | |
2022 年 | OpenBSD 7.1, macOS 13.0 | macOS 12.3 (-f) |
2023 年 | NetBSD 10 | |
2024 年 | POSIX.1-2024で標準化 | POSIX.1-2024で標準化 |
この表から realpath
コマンドの実装が(FreeBSD 版を除き)一回り遅かったことがわかります。また readlink
コマンドも初期の頃は -f
オプションを実装していたものが少なく、-f
オプションが実装されていない macOS はよく問題になっていました。
Solaris 11 では GNU 版の realpath と readlink がインストールされています。AIX では readlink
も realpath
も無いようですが POSIX で標準化されたので、近いうちに実装されるでしょう。無いならないで最初に紹介した readlinkf
関数を使用すればよいです。
realpath はなぜシェルスクリプトにとって重要なのか?
realpath
コマンド(または readlink -f
)はシェルスクリプトにとって重要なコマンドです。その理由は実行しているシェルスクリプトがあるパスを知るためです。
シェルスクリプトをどこからでも実行できるように、シェルスクリプトがある場所に移動するという話を聞きます。これは実は下記の記事で書いたようにアンチパターンです。
シェルスクリプトがある場所に移動する場合、次のようなコードを良く見かけます。
# シェルスクリプトのある場所にカレントディレクトリを移動する
cd "$(dirname "$0")"
# シェルスクリプトのある場所の取得方法
WORKDIR=$(cd "$(dirname "$0")" && pwd)
echo "$WORKDIR"
実は上記のコードはシェルスクリプトがシンボリックリンクの場合に機能しません。例えばディレクトリ構造が次のようになっているとき、test.sh
のシンボリックリンクを実行すると正しくないディレクトリに移動してしまいます。
/tmp/test
|-- bin
| +-- exec.sh -> ../src/test.sh
+-- src
+-- test.sh
+-- file.txt 👈 このファイルを相対パスで読み書きしたい
#!/bin/sh
cd "$(dirname "$0")"
pwd
$ cd /tmp/test
$ ./src/test.sh
/tmp/test/src
$ ./bin/exec.sh 👈 /tmp/test/src/test.sh へのシンボリックリンクを実行
/tmp/test/bin 👈 これでは /tmp/test/src/file.txt が読み書きできない
上記の test.sh
はシンボリックリンクからの実行に対応していないというバグがあります。realpath
が標準化されたので、今後は以下のようにシェルスクリプトから実行しても動くようになる書き方に変化しているのではないかと思います。
#!/bin/sh
set -eu
self=$(realpath -- "$0") # エラーで止まるように一旦変数に入れる
cd "$(dirname -- "self")"
pwd
ただし繰り返しますが、シェルスクリプトがある場所に移動するというのはアンチパターンなので注意してください。
さいごに
かつては readlink
と realpath
コマンドは POSIX で標準化されておらず、実際に移植性がありませんでした。しかし歴史をちゃんと見てみると徐々にシェルスクリプトの世界も便利に変わっているということが分かると思います。昔に使われていてテクニックで現在不要になっているものはいくつもあります。知識のアップデートを行わないと、いつまでも古い効率の悪いやり方を続けてしまいます。