API to get the path to the current executable
01 何が問題だったのか
現在実行中のバイナリ(executable)のパスを取得したい場面は珍しくありません。子プロセスを起動するときに自分自身のパスを渡したい、ユーザーに現在のプログラムについての情報を表示したい、といった用途です。
ところが、C の標準ライブラリにも POSIX にも、このパスを取得する移植性のある方法はありません。各プラットフォームごとに異なる API(macOS の _NSGetExecutablePath()、Linux の /proc/self/exe の読み取り、Windows の GetModuleFileNameW() など)を自前で呼び分ける必要があり、開発者はそのたびに似たようなプラットフォーム固有コードを書くことになります。
安易な代替として CommandLine.arguments[0](= C の argv[0])を使う例も見かけますが、これは誤りです。C23 の規格では argv[0] は「プログラム名」を表すだけで、実行ファイルへのパスを含むことは要求されていません。実際には親プロセスが自由に設定でき、相対パスや部分的なパス、あるいは実行ファイルとは無関係な文字列が入っていることすらあります。
Swift 本体でも、コマンドライン引数は CommandLine.arguments としてプラットフォームごとに適切に取得されています。executable のパスも同様に、Swift 標準ライブラリがプラットフォーム差異を吸収して提供するのが自然です。実際、Foundation、Swift Argument Parser、Swift Testing、swift-subprocess など、Swift ツールチェイン内外の複数のモジュールがこの情報を必要としています。
02 どのように解決されるのか
標準ライブラリの CommandLine 型に、現在の実行ファイルへのパスを返す読み取り専用プロパティ executablePath を追加します。
extension CommandLine {
public static var executablePath: String? { get }
}
使い方は単純で、実行中のバイナリのパスを String? として受け取るだけです。
if let path = CommandLine.executablePath {
print("running from \(path)")
} else {
print("executable path is unavailable")
}
値の扱い方
返される文字列は 正規化されたパスとは限りません。シンボリックリンクが解決されているとも限らず、相対要素が残っている可能性もあります。正規化したいときは、このパスを realpath()(Windows では _wfullpath())に渡すか、URL の API で standardize します。
シンボリックリンクをあえて解決しないのは意図的な設計です。多くのケースでシンボリックリンクは存在せず、毎回の I/O が無駄になるためです。もし存在したとしても、そのことが呼び出し側にとって必ずしも問題になるわけではありません。必要な呼び出し側だけが、自分で realpath() などを呼べばよい、という方針です。
ただし Linux のように「この API 自体がシンボリックリンク(/proc/self/exe)を辿ることで実装されている」プラットフォームでは、必要に迫られてその部分だけは解決されます。
失敗時と可用性
パスを取得できなかった場合は nil が返ります。戻り値を optional にしたのは、executable パスの取得失敗が必ずしもプログラム全体の致命的エラーを意味するわけではなく、呼び出し側に回復の余地を残したいためです。throwing にはしません。失敗はいずれもエッジケースで、エラーを投げるのは重すぎ、結局 try? で握りつぶされるだけになる可能性が高いからです。
実行中にバイナリがディスク上で移動された場合、このプロパティが新しいパスに追従するかどうかはプラットフォーム依存で、Swift 側では保証できません。ドキュメントでその可能性が注記されます。
Embedded Swift と WASI では @available(*, unavailable) によって コンパイル時に利用不可 とされます。WASI には WebAssembly 仮想マシン内に「実行ファイル」の概念が明確には存在せず、Embedded Swift ではそもそもファイルシステムが無いターゲットもあり得るためです。いずれも将来サポート可能になれば制限は外しうる、という位置づけです。
なお Darwin では、このプロパティは OS 既存 API の上に実装されるため back-deploy 可能で、古い OS バージョンでも利用できます。