Qt QFileInfo::canonicalPath()徹底解説:パス正規化の基本と活用

2025-05-31

QFileInfo::canonicalPath() とは

QFileInfo::canonicalPath() は、Qt の QFileInfo クラスのメンバー関数で、ファイルシステム上のディレクトリの「正規化されたパス」を返します。ここでいう「正規化されたパス」とは、以下の要素が排除されたパスのことです。

  1. シンボリックリンク (Symbolic links) の解決
    もしパスがシンボリックリンクを含んでいる場合、そのリンクが指し示す実際のパスに解決されます。
  2. 冗長な . (カレントディレクトリ) や .. (親ディレクトリ) の排除
    例えば、/home/user/./documents/../projects のようなパスがあった場合、これを /home/user/projects のように簡潔な形式に変換します。

この関数は、ファイル名を含まず、ディレクトリのパスのみを返します。ファイル名を含む正規化されたパスが必要な場合は、QFileInfo::canonicalFilePath() を使用します。

  • シンボリックリンクのないシステム
    シンボリックリンクをサポートしないシステム(一部のWindows環境など)では、canonicalPath()absolutePath() と同じ文字列を返します。
  • 存在しないパスの場合
    指定されたパス(またはシンボリックリンクの解決後のパス)がファイルシステム上に存在しない場合、canonicalPath() は空の文字列を返します。これは、パスが指し示す実体がない、またはシンボリックリンクが壊れている(dangling symbolic link)ことを意味します。
  • ファイルシステムへの問い合わせ
    この関数は、シンボリックリンクの解決や存在確認のために、実際にファイルシステムに問い合わせを行います。そのため、QFileInfo::path()QFileInfo::absolutePath() のように単に文字列操作でパスを返す関数よりも処理に時間がかかる場合があります。
  • 絶対パスを返す
    canonicalPath() は常に絶対パスを返します。相対パスが指定された場合でも、現在の作業ディレクトリを基準にして絶対パスに変換し、さらに正規化を行います。

使用例

#include <QCoreApplication>
#include <QFileInfo>
#include <QDir>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // 例1: 冗長なパスの正規化
    QFileInfo fileInfo1("../temp/./../documents/my_file.txt");
    qDebug() << "Original path:" << fileInfo1.path(); // "../temp/./../documents"
    qDebug() << "Canonical path:" << fileInfo1.canonicalPath(); // (現在のディレクトリによって結果が異なるが、簡潔な絶対パスになる)

    // 例2: シンボリックリンクの解決 (Unix系システムの場合)
    // /usr/bin が /usr/local/bin へのシンボリックリンクだと仮定
    QFileInfo fileInfo2("/usr/bin/ls");
    qDebug() << "Original path (symlink):" << fileInfo2.path(); // "/usr/bin"
    qDebug() << "Canonical path (symlink):" << fileInfo2.canonicalPath(); // (例えば "/usr/local/bin" のようになる)

    // 例3: 存在しないパス
    QFileInfo fileInfo3("/nonexistent/directory/file.txt");
    qDebug() << "Non-existent path:" << fileInfo3.path(); // "/nonexistent/directory"
    qDebug() << "Canonical path (non-existent):" << fileInfo3.canonicalPath(); // "" (空文字列)

    // 例4: Windowsでのドライブレターを含むパス
    // 仮に "C:/Users/Public/Documents" が存在するディレクトリとする
    QFileInfo fileInfo4("C:/Users/Public/./Documents");
    qDebug() << "Original path (Windows):" << fileInfo4.path(); // "C:/Users/Public/./Documents"
    qDebug() << "Canonical path (Windows):" << fileInfo4.canonicalPath(); // "C:/Users/Public/Documents"

    return 0;
}
Original path: "../temp/./../documents"
Canonical path: "/home/user/documents" // または C:/Users/YourUser/documents など

Original path (symlink): "/usr/bin"
Canonical path (symlink): "/usr/local/bin" // もしくは "/usr/bin" (シンボリックリンクがない場合)

Non-existent path: "/nonexistent/directory"
Canonical path (non-existent): ""

Original path (Windows): "C:/Users/Public/./Documents"
Canonical path (Windows): "C:/Users/Public/Documents"


QFileInfo::canonicalPath() の一般的なエラーとトラブルシューティング

空の文字列が返される ("")

これは最もよくある問題です。canonicalPath() が空の文字列を返す場合、以下のいずれかの理由が考えられます。

  • 原因
    アクセス権がない

    • プログラムが、指定されたパス(またはその親ディレクトリ)にアクセスするための適切な読み取り権限を持っていない場合、ファイルシステムへの問い合わせが失敗し、空の文字列が返されることがあります。
    • トラブルシューティング
      • 対象のファイルやディレクトリのアクセス権を確認します。
      • アプリケーションを管理者権限(Windows)またはroot権限(Unix/Linux)で実行して試してみます(一時的なテスト目的のみ)。
      • 権限の問題であれば、権限を調整するか、適切なアクセス権を持つユーザーでアプリケーションを実行する必要があります。
  • 原因
    シンボリックリンクが壊れている(Dangling Symbolic Link)

    • パスがシンボリックリンクを含んでおり、そのシンボリックリンクが指し示す先のファイルやディレクトリが存在しない場合、canonicalPath() は解決に失敗し、空の文字列を返します。
    • トラブルシューティング
      • QFile::exists(path) は、シンボリックリンク自体が存在すれば true を返しますが、canonicalPath() はリンク先の存在まで確認します。
      • Unix/Linux システムの場合、readlink コマンドなどでシンボリックリンクのリンク先を確認します。
      • プログラムでシンボリックリンクのリンク先を取得したい場合は、QFile::symLinkTarget() を使用します。
      • 例:
        // /path/to/broken_link が存在するが、そのリンク先がない場合
        QFileInfo fileInfo("/path/to/broken_link");
        qDebug() << "Path exists?" << fileInfo.exists(); // true (リンク自体は存在する)
        qDebug() << "Canonical path:" << fileInfo.canonicalPath(); // "" (リンク先がないため)
        
  • 原因
    パスが存在しない

    • canonicalPath() は、指定されたパス(またはシンボリックリンクを解決した後のパス)がファイルシステム上に実際に存在しない場合、空の文字列を返します。
    • トラブルシューティング
      • QFile::exists(path)QFileInfo::exists() を使って、パスが存在するかどうかを確認します。
      • タイプミスやパスの構築ミスがないか、パス文字列をデバッグ出力して確認します。
      • 相対パスを使用している場合、現在の作業ディレクトリが期待通りになっているか QDir::currentPath() で確認します。
      • 例:
        QFileInfo fileInfo("/nonexistent/path/to/dir");
        qDebug() << "Path exists?" << fileInfo.exists(); // false
        qDebug() << "Canonical path:" << fileInfo.canonicalPath(); // ""
        

期待と異なるパスが返される

  • 原因
    ネットワークパス(UNCパス)やマウントポイント

    • ネットワークドライブ(UNCパス \\server\share)やNFS/SMBなどのマウントポイントをcanonicalPath()で処理しようとすると、期待通りの動作をしない場合があります。これは、基盤となるOSのAPIがネットワークパスの解決をどのようにサポートしているかに依存します。
    • トラブルシューティング
      • UNCパスの場合、QFileInfo は通常それらを直接扱えますが、その「正規」表現が何を意味するかはシステムによって異なります。
      • ネットワークパスの解決が問題となる場合は、そのパスがローカルにマッピングされているかどうかを確認したり、QDir::toNativeSeparators() などを使ってOSネイティブのパス形式に変換してから試したりすることを検討します。
      • 場合によっては、ネットワークリソースに特化したAPI(例: WindowsのWNet functions)を使用する必要があるかもしれません。
  • 原因
    大文字・小文字の区別(Windows vs. Unix/Linux)

    • Windowsではパス名の大文字・小文字は通常区別されませんが、Unix/Linuxでは区別されます。canonicalPath() はファイルシステムの実体を反映するため、大文字・小文字の区別があるシステムでは、指定されたパスが間違った大文字・小文字を使っていると、異なるパスと認識されたり、存在しないと判断されたりする可能性があります。
    • トラブルシューティング
      • パスを扱う際には、可能な限り大文字・小文字を区別する環境としない環境の両方を考慮に入れる設計をします。
      • ユーザー入力など、信頼できないパスを受け取る場合は、canonicalPath() の結果を常に確認し、必要に応じてエラー処理を行います。
  • 原因
    シンボリックリンクの解釈

    • canonicalPath() はシンボリックリンクを解決するため、例えば /home/user/my_link というパスが /var/data/real_data を指している場合、canonicalPath()/var/data を返します。これが期待と異なる場合、誤解があるかもしれません。
    • トラブルシューティング
      • シンボリックリンクの解決を望まない場合は、QFileInfo::absolutePath() または QFileInfo::path() を使用します。
      • 例:
        // /home/user/link_to_data が /mnt/shared/data を指しているとする
        QFileInfo fileInfo("/home/user/link_to_data/file.txt");
        qDebug() << "Original path:" << fileInfo.path(); // "/home/user/link_to_data"
        qDebug() << "Absolute path:" << fileInfo.absolutePath(); // "/home/user/link_to_data" (絶対パスに変換)
        qDebug() << "Canonical path:" << fileInfo.canonicalPath(); // "/mnt/shared/data" (シンボリックリンクを解決)
        

パフォーマンスの問題

  • 原因
    canonicalPath() はファイルシステムに問い合わせる
    • 前述の通り、canonicalPath() はパスの存在確認やシンボリックリンクの解決のために、実際にファイルシステムにアクセスします。そのため、頻繁に呼び出したり、ネットワークドライブ上のパスに対して呼び出したりすると、パフォーマンスが低下する可能性があります。
    • トラブルシューティング
      • canonicalPath() を不必要に頻繁に呼び出すのを避けます。
      • パスの正規化が必要ない場合は、より軽量な QFileInfo::path()QFileInfo::absolutePath() を使用します。
      • 大量のファイルパスを処理する場合は、一度にすべての canonicalPath() を呼び出すのではなく、必要になった時点で遅延評価するように設計を変更することを検討します。
      • ネットワークパスの場合、ローカルキャッシュを利用したり、ネットワークI/Oを最小限に抑える工夫をします。
  • 公式ドキュメントの参照
    QFileInfo::canonicalPath() のQt公式ドキュメントを再確認し、特に「Note」や「Warning」セクションに記載されている注意点を確認します。
  • Qtのバージョン
    使用しているQtのバージョンが古い場合、バグが修正されている可能性があるので、最新のLTSバージョンなどを確認します。
  • OS環境の確認
    Windows、macOS、Linuxなど、異なるOS環境で動作を確認し、OS固有の挙動(大文字・小文字の区別、パスのセパレータなど)が影響していないかを調べます。
  • シンプルなテストケース
    問題が複雑なパスで発生する場合は、まず非常に単純なパス(例: ".", "..", "/tmp")で試してみて、基本的な動作を確認します。
  • デバッグ出力
    qDebug() を使って、元のパス、exists() の結果、path()absolutePath()canonicalPath() の結果などを段階的に出力し、どの時点で問題が発生しているかを特定します。


QFileInfo::canonicalPath() は、ファイルシステムのパスを「正規化」するために使われます。これには、シンボリックリンクの解決、冗長なパス要素(...)の削除、そして常に絶対パスを返すという特徴があります。

冗長なパス要素の削除と絶対パス化

最も基本的な使用例は、ユーザーが入力したパスや、スクリプトで生成されたパスに ... のような冗長な要素が含まれている場合に、それをクリーンアップして絶対パスに変換することです。

コード例

#include <QCoreApplication>
#include <QFileInfo>
#include <QDir>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // 現在の作業ディレクトリが /home/user/my_app だと仮定
    QDir::setCurrent("/home/user/my_app");
    qDebug() << "Current working directory:" << QDir::currentPath();

    QString relativePath = "../data/./configs/../settings";
    QFileInfo fileInfo(relativePath);

    qDebug() << "Original path (relative):" << fileInfo.path();
    qDebug() << "Absolute path (without canonicalization):" << fileInfo.absolutePath();
    qDebug() << "Canonical path (normalized):" << fileInfo.canonicalPath();

    // 存在しないパスの場合
    QString nonExistentPath = "/nonexistent/dir/../another_nonexistent";
    QFileInfo nonExistentFileInfo(nonExistentPath);
    qDebug() << "\n--- Non-existent Path Example ---";
    qDebug() << "Non-existent path:" << nonExistentFileInfo.path();
    qDebug() << "Canonical path (non-existent):" << nonExistentFileInfo.canonicalPath(); // 空文字列が返る
    qDebug() << "Path exists?" << nonExistentFileInfo.exists(); // false

    return 0;
}

出力例 (Unix/Linux環境の場合)

Current working directory: "/home/user/my_app"
Original path (relative): "../data/./configs/../settings"
Absolute path (without canonicalization): "/home/user/my_app/../data/./configs/../settings"
Canonical path (normalized): "/home/user/data"

--- Non-existent Path Example ---
Non-existent path: "/nonexistent/dir/../another_nonexistent"
Canonical path (non-existent): ""
Path exists? false

解説

  • 存在しないパス (nonExistentPath) の場合、canonicalPath() は空の文字列を返します。これは、この関数がパスの実体をファイルシステム上で確認するためです。
  • fileInfo.canonicalPath() は、現在の作業ディレクトリを基準に絶対パスに変換し、さらに ... を解決して、最終的に /home/user/data という簡潔なパスを返します。
  • fileInfo.absolutePath() は、相対パスを現在の作業ディレクトリに基づいて絶対パスに変換しますが、冗長な要素はそのまま残ります。
  • fileInfo.path() は、単に QFileInfo に渡された文字列のディレクトリ部分を返します。
  • relativePath../data/./configs/../settings のように、親ディレクトリ (..) やカレントディレクトリ (.)、さらには余分な .. を含む複雑なパスです。

シンボリックリンクの解決

canonicalPath() のもう一つの重要な機能は、パスに含まれるシンボリックリンクを解決し、それが指し示す実際のパスを返すことです。これは、Unix/Linux 環境で特に重要です。

前提
この例を実行する前に、以下のコマンドでシンボリックリンクを作成してください。

# 適当な場所に /tmp/real_target_dir を作成
mkdir -p /tmp/real_target_dir
echo "This is a test file." > /tmp/real_target_dir/test_file.txt

# /tmp/symlink_to_dir を /tmp/real_target_dir へのシンボリックリンクとして作成
ln -s /tmp/real_target_dir /tmp/symlink_to_dir

コード例

#include <QCoreApplication>
#include <QFileInfo>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // シンボリックリンクを指すパス
    QString symlinkPath = "/tmp/symlink_to_dir/test_file.txt";
    QFileInfo fileInfo(symlinkPath);

    qDebug() << "Original path (symlink):" << fileInfo.filePath(); // "/tmp/symlink_to_dir/test_file.txt"
    qDebug() << "Path (directory part of symlink):" << fileInfo.path(); // "/tmp/symlink_to_dir"
    qDebug() << "Canonical path (resolves symlink):" << fileInfo.canonicalPath(); // "/tmp/real_target_dir"

    // シンボリックリンク自体が存在するか
    qDebug() << "Symlink exists?" << fileInfo.exists(); // true

    // シンボリックリンクのリンク先が存在するか (canonicalPath()の結果から確認)
    QFileInfo canonicalFileInfo(fileInfo.canonicalPath());
    qDebug() << "Canonical path exists?" << canonicalFileInfo.exists(); // true

    // 壊れたシンボリックリンクの場合
    // 事前に /tmp/broken_link が存在するが、そのリンク先がないように設定
    // 例: ln -s /nonexistent/target /tmp/broken_link
    QString brokenSymlinkPath = "/tmp/broken_link/some_file.txt";
    QFileInfo brokenFileInfo(brokenSymlinkPath);
    qDebug() << "\n--- Broken Symlink Example ---";
    qDebug() << "Original path (broken symlink):" << brokenFileInfo.filePath();
    qDebug() << "Symlink exists?" << brokenFileInfo.exists(); // true (リンク自体は存在する)
    qDebug() << "Canonical path (broken symlink):" << brokenFileInfo.canonicalPath(); // 空文字列が返る
    qDebug() << "Canonical path exists?" << QFileInfo(brokenFileInfo.canonicalPath()).exists(); // false

    return 0;
}

出力例 (Unix/Linux環境の場合)

Original path (symlink): "/tmp/symlink_to_dir/test_file.txt"
Path (directory part of symlink): "/tmp/symlink_to_dir"
Canonical path (resolves symlink): "/tmp/real_target_dir"
Symlink exists? true
Canonical path exists? true

--- Broken Symlink Example ---
Original path (broken symlink): "/tmp/broken_link/some_file.txt"
Symlink exists? true
Canonical path (broken symlink): ""
Canonical path exists? false

解説

  • 壊れたシンボリックリンク(リンク先が存在しないシンボリックリンク)の場合、canonicalPath() は空の文字列を返します。これは、リンク先の実体が存在しないため、正規化されたパスを特定できないからです。
  • fileInfo.canonicalPath() は、シンボリックリンクを解決し、実際にファイルがあるディレクトリのパスである /tmp/real_target_dir を返します。
  • fileInfo.path() は、QFileInfo に渡された文字列のディレクトリ部分である /tmp/symlink_to_dir をそのまま返します。
  • symlinkPath/tmp/symlink_to_dir というシンボリックリンクを含むパスです。

パスの比較における利用

canonicalPath() は、異なる記述のパスが実際には同じ場所を指しているかどうかを比較する際に非常に役立ちます。

コード例

#include <QCoreApplication>
#include <QFileInfo>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // 同じディレクトリを指す異なるパス
    QString path1 = "/home/user/./documents/../data";
    QString path2 = "/home/user/data";
    // 仮に /var/www/html が /srv/web_root へのシンボリックリンクだとする
    QString path3 = "/var/www/html";
    QString path4 = "/srv/web_root";

    QFileInfo fi1(path1);
    QFileInfo fi2(path2);
    QFileInfo fi3(path3);
    QFileInfo fi4(path4);

    qDebug() << "Path1 canonical:" << fi1.canonicalPath();
    qDebug() << "Path2 canonical:" << fi2.canonicalPath();
    qDebug() << "Path3 canonical:" << fi3.canonicalPath();
    qDebug() << "Path4 canonical:" << fi4.canonicalPath();

    if (!fi1.canonicalPath().isEmpty() && !fi2.canonicalPath().isEmpty() &&
        fi1.canonicalPath() == fi2.canonicalPath()) {
        qDebug() << "\nPath1 and Path2 refer to the same directory.";
    } else {
        qDebug() << "\nPath1 and Path2 refer to different directories or one doesn't exist.";
    }

    if (!fi3.canonicalPath().isEmpty() && !fi4.canonicalPath().isEmpty() &&
        fi3.canonicalPath() == fi4.canonicalPath()) {
        qDebug() << "Path3 and Path4 refer to the same directory.";
    } else {
        qDebug() << "Path3 and Path4 refer to different directories or one doesn't exist.";
    }

    return 0;
}

出力例 (シンボリックリンクの前提が満たされている場合)

Path1 canonical: "/home/user/data"
Path2 canonical: "/home/user/data"
Path3 canonical: "/srv/web_root"
Path4 canonical: "/srv/web_root"

Path1 and Path2 refer to the same directory.
Path3 and Path4 refer to the same directory.
  • 同様に、シンボリックリンクを含む path3 と、それが指し示す実体である path4 も、canonicalPath() を使うことで同じディレクトリを参照していると判断できます。
  • path1path2 は記述は異なりますが、canonicalPath() で正規化すると同じパス /home/user/data になります。これにより、これらが同じディレクトリを指していることを正確に比較できます。


以下に、代替となる方法とその使い分けを説明します。

QFileInfo::absolutePath() または QFileInfo::path()

  • コード例
    #include <QCoreApplication>
    #include <QFileInfo>
    #include <QDir>
    #include <QDebug>
    
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
    
        QDir::setCurrent("/home/user/my_app"); // 仮の現在の作業ディレクトリ
    
        QString relativePath = "../data/./configs/../settings/file.txt";
        QFileInfo fileInfo(relativePath);
    
        qDebug() << "Original path:" << fileInfo.filePath(); // "../data/./configs/../settings/file.txt"
        qDebug() << "Path (directory part):" << fileInfo.path(); // "../data/./configs/../settings"
        qDebug() << "Absolute path (no canonicalization):" << fileInfo.absolutePath(); // "/home/user/my_app/../data/./configs/../settings"
    
        return 0;
    }
    
  • 使い分け
    • 単に絶対パスが必要で、シンボリックリンクや冗長な要素をそのままにしておきたい場合に最適です。
    • パスの見た目を保持したい場合にも使えます。
  • 特徴
    • QFileInfo::absolutePath(): 指定されたパス(相対パスの場合)を現在の作業ディレクトリに基づいて絶対パスに変換します。シンボリックリンクは解決しません。
    • QFileInfo::path(): QFileInfo オブジェクトに渡された元のパス文字列から、ファイル名部分を除いたディレクトリパスを返します。相対パスのままです。
    • どちらもファイルシステムへのアクセスを伴わない文字列操作であるため、canonicalPath() よりも高速です。
  • 目的
    パスを絶対パスに変換したいが、シンボリックリンクの解決や ... の削除は不要な場合。

QDir::cleanPath()

  • コード例
    #include <QCoreApplication>
    #include <QDir>
    #include <QDebug>
    
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
    
        QString path1 = "/path/to/./some/../dir/";
        QString path2 = "relative/./path/../to/file.txt";
        QString path3 = "//another///path";
        QString path4 = "/symlink/target/./../another_dir"; // シンボリックリンクは解決しない
    
        qDebug() << "Original path1:" << path1;
        qDebug() << "Cleaned path1:" << QDir::cleanPath(path1); // "/path/to/dir"
    
        qDebug() << "\nOriginal path2:" << path2;
        qDebug() << "Cleaned path2:" << QDir::cleanPath(path2); // "relative/to/file.txt"
    
        qDebug() << "\nOriginal path3:" << path3;
        qDebug() << "Cleaned path3:" << QDir::cleanPath(path3); // "/another/path"
    
        qDebug() << "\nOriginal path4 (symlink is NOT resolved):" << path4;
        qDebug() << "Cleaned path4 (symlink is NOT resolved):" << QDir::cleanPath(path4); // "/symlink/another_dir"
    
        return 0;
    }
    
  • 使い分け
    • ファイルシステムへのアクセスなしにパス文字列をクリーンアップしたい場合に非常に有効です。
    • canonicalPath() のパフォーマンスオーバーヘッドを避けたいが、パスを簡潔にしたい場合に最適です。
    • シンボリックリンクを解決する必要がない場合に適しています。
  • 特徴
    • 静的関数であり、ファイルシステムへのアクセスは伴いません。
    • 相対パスが渡されれば相対パスとしてクリーンアップし、絶対パスが渡されれば絶対パスとしてクリーンアップします。
    • 末尾のスラッシュの有無や、複数のスラッシュ、./../ を処理します。
  • 目的
    パス文字列から冗長な要素(./, ../, // など)を削除し、パスを簡潔にしたい場合。シンボリックリンクの解決は行いません。

QFileInfo::symLinkTarget() (シンボリックリンクのターゲットを取得する場合)