パス文字列の中の '.' や '..' を取り除いたり、シンボリックリンクを展開してパスを正規化するには realpath
関数が使えます。
echo realpath('/etc/./passwd') . "\n"; // => /etc/passwd
ただし、 realpath
関数はファイルが存在しない場合には FALSE
を返します。
ファイルが存在しない場合にもパスを正規化するには自前で関数を定義します。
以下のコードはファイルの存在有無に関わらずパスを正規化するサンプルコードです。シンボリックリンクの展開には対応していませんが、'.' や '..' は取り除いてくれます。
<?php
function normalize_path($path)
{
// 絶対パスでなければカレントディレクトリとつなげる
if (strpos($path, '/') !== 0) {
$path = getcwd() . '/' . $path;
}
// パスを '/' で区切って分けた配列
$fragments = [];
// パースのための一時変数
$fragment = '';
for ($i = 0, $l = strlen($path); $i < $l; $i++) {
$c = substr($path, $i, 1);
// '\' はエスケープ文字
// 見つけたら次の文字が '/' でもパスの区切り文字とは見なさない
if ($c === '\\') {
$fragment .= $c;
$i++;
$c = substr($path, $i, 1);
$fragment .= $c;
continue;
}
// '/' を見つけたとき
if ($c === '/') {
// './' や '/' の連続は無視する
if ($fragment === '' || $fragment === '.') {
$fragment = '';
continue;
}
// '..' だったらディレクトリをさかのぼる
if ($fragment === '..') {
array_pop($fragments);
$fragment = '';
continue;
}
// 配列に追加する
$fragments[] = $fragment;
$fragment = '';
continue;
}
$fragment .= $c;
}
$fragments[] = $fragment;
return '/' . implode('/', $fragments);
}
echo normalize_path('/var/www/html') . "\n";
echo normalize_path('/var/www/./html') . "\n";
echo normalize_path('/var/www/../html') . "\n";
echo normalize_path('/var/www/../../html') . "\n";
このコードを実行すると次のような出力となります。
/var/www/html
/var/www/html
/var/html
/html