5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【POSIX準拠】readlink・realpath コマンドの使い分けと歴史

Last updated at Posted at 2023-12-16

はじめに

readlink コマンドと realpath コマンドは、どちらもシンボリックリンクのリンク先を調べるコマンドです。readlink -frealpath とほぼ同等です。

  • 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 -frealpath もほとんどの環境で使うことができるようになりました。

POSIX で標準化された readlinkrealpath

POSIX で標準化された readlinkrealpath はオプションが少なく使い方も簡単です。

シンボリックリンクのリンク先を取得する(再帰的に取得するのではない)
readlink [-n] file

-n: 末尾の改行を出力しない
シンボリックリンクを再帰的に解決して実体のパスを取得する
realpath [-e | -E] file

-e: ファイルが見つからない場合にエラーにする
-E: ファイルが見つからない場合にエラーにしない

POSIX 標準化された範囲では readlink は再帰的にシンボリックリンクのパスを解決するものではなく「シンボリックリンクファイルのリンク先を取得するだけ」のコマンドです。再帰的にパスを解決するのではなく単純にシンボリックリンクファイルの中身(リンク先)を取得するだけです。名前と機能がうまくマッチしていると思います。

実際の実装はもっと多くのオプションを持ち、readlinkrealpath は同じように使うことができたため、使い分けに混乱していたのですが、POSIX の標準化の内容ではそれがなくなりました。私は最初、POSIX はなぜ似たようなコマンドを二つとも標準化したのか疑問だったのですが、よく見ると役割を明確に分けたということがわかります。

readlink コマンドと realpath コマンドの歴史

readlinkrealpath はどちらも古くからあるコマンドです。どちらもシステムコールまたは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 版の realpathreadlink がインストールされています。AIX では readlinkrealpath も無いようですが 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    👈 このファイルを相対パスで読み書きしたい
/tmp/test/src/test.sh
#!/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 が標準化されたので、今後は以下のようにシェルスクリプトから実行しても動くようになる書き方に変化しているのではないかと思います。

/tmp/test/src/test.sh
#!/bin/sh
set -eu
self=$(realpath -- "$0") # エラーで止まるように一旦変数に入れる
cd "$(dirname -- "self")"
pwd

ただし繰り返しますが、シェルスクリプトがある場所に移動するというのはアンチパターンなので注意してください。

さいごに

かつては readlinkrealpath コマンドは POSIX で標準化されておらず、実際に移植性がありませんでした。しかし歴史をちゃんと見てみると徐々にシェルスクリプトの世界も便利に変わっているということが分かると思います。昔に使われていてテクニックで現在不要になっているものはいくつもあります。知識のアップデートを行わないと、いつまでも古い効率の悪いやり方を続けてしまいます。

5
3
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?