• 首页
  • vue
  • TypeScript
  • JavaScript
  • scss
  • css3
  • html5
  • php
  • MySQL
  • redis
  • jQuery
  • realpath()

    (PHP 4, PHP 5, PHP 7)

    返回规范化的绝对路径名

    说明

    realpath(string $path): string

    realpath()扩展所有的符号连接并且处理输入的$path中的'/./','/../'以及多余的'/'并返回规范化后的绝对路径名。返回的路径中没有符号连接,'/./'或'/../'成分。

    参数

    $path

    要检查的路径。

    Note:

    Whilst a path must be supplied, the value can be blank orNULLIn these cases, the value is interpreted as the current directory.

    返回值

    Returns the canonicalized absolute pathname on success. The resulting path will have no symbolic link,'/./' or '/../' components.

    realpath()失败时返回FALSE,比如说文件不存在的话。

    Note:

    The running script must have executable permissions on all directories in the hierarchy,otherwiserealpath()will returnFALSE.

    Note:因为 PHP 的整数类型是有符号整型而且很多平台使用 32 位整型,对 2GB以上的文件,一些文件系统函数可能返回无法预期的结果。

    更新日志

    版本说明
    5.3.0在之前的版本中,在*BSD 系统上,如果仅仅是$path不存在的话,realpath()并不会像其它系统那样返回FALSE
    5.0.0在此之前的版本中,如果$path传入了空或者NULL,将导致realpath()返回脚本当前的目录。

    范例

    Example #1realpath()例子

    <?php
    chdir('/var/www/');
    echo realpath('./../../etc/passwd');
    ?>
    

    以上例程会输出:

    /etc/passwd
    

    Windows 上的realpath()

    在 Windows 上,realpath()会将 unix 风格的路径改成 Windows 风格的。

    <?php
    echo realpath('/windows/system32');
    ?>
    

    以上例程会输出:

    C:\WINDOWS\System32
    

    参见

    Because realpath() does not work on files that do not
    exist, I wrote a function that does.
    It replaces (consecutive) occurences of / and \\ with
    whatever is in DIRECTORY_SEPARATOR, and processes /. and /.. fine.
    Paths returned by get_absolute_path() contain no
    (back)slash at position 0 (beginning of the string) or
    position -1 (ending)
    <?php
      function get_absolute_path($path) {
        $path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path);
        $parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen');
        $absolutes = array();
        foreach ($parts as $part) {
          if ('.' == $part) continue;
          if ('..' == $part) {
            array_pop($absolutes);
          } else {
            $absolutes[] = $part;
          }
        }
        return implode(DIRECTORY_SEPARATOR, $absolutes);
      }
    ?>
    A test:
    <?php
      var_dump(get_absolute_path('this/is/../a/./test/.///is'));
    ?>
    Returns: string(14) "this/a/test/is" 
    As you can so, it also produces Yoda-speak. :)
    Needed a method to normalize a virtual path that could handle .. references that go beyond the initial folder reference. So I created the following.
    <?php
    function normalizePath($path)
    {
      $parts = array();// Array to build a new path from the good parts
      $path = str_replace('\\', '/', $path);// Replace backslashes with forwardslashes
      $path = preg_replace('/\/+/', '/', $path);// Combine multiple slashes into a single slash
      $segments = explode('/', $path);// Collect path segments
      $test = '';// Initialize testing variable
      foreach($segments as $segment)
      {
        if($segment != '.')
        {
          $test = array_pop($parts);
          if(is_null($test))
            $parts[] = $segment;
          else if($segment == '..')
          {
            if($test == '..')
              $parts[] = $test;
            if($test == '..' || $test == '')
              $parts[] = $segment;
          }
          else
          {
            $parts[] = $test;
            $parts[] = $segment;
          }
        }
      }
      return implode('/', $parts);
    }
    ?>
    Will convert /path/to/test/.././..//..///..///../one/two/../three/filename
    to ../../one/three/filename
    <?php
    namespace MockingMagician\Organic\Helper;
    class Path
    {
      /**
       * There is a method that deal with Sven Arduwie proposal https://www.php.net/manual/en/function.realpath.php#84012
       * And runeimp at gmail dot com proposal https://www.php.net/manual/en/function.realpath.php#112367
       * @param string $path
       * @return string
       */
      public static function getAbsolute(string $path): string
      {
        // Cleaning path regarding OS
        $path = mb_ereg_replace('\\\\|/', DIRECTORY_SEPARATOR, $path, 'msr');
        // Check if path start with a separator (UNIX)
        $startWithSeparator = $path[0] === DIRECTORY_SEPARATOR;
        // Check if start with drive letter
        preg_match('/^[a-z]:/', $path, $matches);
        $startWithLetterDir = isset($matches[0]) ? $matches[0] : false;
        // Get and filter empty sub paths
        $subPaths = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'mb_strlen');
        $absolutes = [];
        foreach ($subPaths as $subPath) {
          if ('.' === $subPath) {
            continue;
          }
          // if $startWithSeparator is false
          // and $startWithLetterDir
          // and (absolutes is empty or all previous values are ..)
          // save absolute cause that's a relative and we can't deal with that and just forget that we want go up
          if ('..' === $subPath
            && !$startWithSeparator
            && !$startWithLetterDir
            && empty(array_filter($absolutes, function ($value) { return !('..' === $value); }))
          ) {
            $absolutes[] = $subPath;
            continue;
          }
          if ('..' === $subPath) {
            array_pop($absolutes);
            continue;
          }
          $absolutes[] = $subPath;
        }
        return
          (($startWithSeparator ? DIRECTORY_SEPARATOR : $startWithLetterDir) ?
            $startWithLetterDir.DIRECTORY_SEPARATOR : ''
          ).implode(DIRECTORY_SEPARATOR, $absolutes);
      }
      /**
       * Examples
       *
       * echo Path::getAbsolute('/one/two/../two/./three/../../two');      =>  /one/two
       * echo Path::getAbsolute('../one/two/../two/./three/../../two');     =>  ../one/two
       * echo Path::getAbsolute('../.././../one/two/../two/./three/../../two'); =>  ../../../one/two
       * echo Path::getAbsolute('../././../one/two/../two/./three/../../two');  =>  ../../one/two
       * echo Path::getAbsolute('/../one/two/../two/./three/../../two');     =>  /one/two
       * echo Path::getAbsolute('/../../one/two/../two/./three/../../two');   =>  /one/two
       * echo Path::getAbsolute('c:\.\..\one\two\..\two\.\three\..\..\two');   =>  c:/one/two
       *
       */
    }
    realpath() is just a system/library call to actual realpath() function supported by OS. It does not work on a path as a string, but also resolves symlinks. The resulting path might significantly differs from the input even when absolute path is given. No function in this notes resolves that.
    The suggestion on the realpath man page is to look for an existing parent directory. Here is an example:
    <?php
      function resolvePath($path) {
        if(DIRECTORY_SEPARATOR !== '/') {
          $path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
        }
        $search = explode('/', $path);
        $search = array_filter($search, function($part) {
          return $part !== '.';
        });
        $append = array();
        $match = false;
        while(count($search) > 0) {
          $match = realpath(implode('/', $search));
          if($match !== false) {
            break;
          }
          array_unshift($append, array_pop($search));
        };
        if($match === false) {
          $match = getcwd();
        }
        if(count($append) > 0) {
          $match .= DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $append);
        }
        return $match;
      }
    ?>
    The result will retrieve absolute path for non-existing relative path. Even if a path does not exists, there should be existing directory somewhere, for which the realpath could be obtained. If this is not within the relative path (i.e. even current working directory does not exists), getcwd() will retrieve absolute path, so some absolute path is returned (although in that case the PHP process could have huge problems).
    Note: If you use this to check if a file exists, it's path will be cached, and returns true even if the file is removed (use file_exists instead).
    Here's a function to canonicalize a URL containing relative paths. Ran into the problem when pulling links from a remote page.
    <?php
    function canonicalize($address)
    {
      $address = explode('/', $address);
      $keys = array_keys($address, '..');
      foreach($keys AS $keypos => $key)
      {
        array_splice($address, $key - ($keypos * 2 + 1), 2);
      }
      $address = implode('/', $address);
      $address = str_replace('./', '', $address);
    }
    $url = 'http://www.example.com/something/../else';
    echo canonicalize($url); //http://www.example.com/else
    ?>
    
    It should probably be expressly noted that tilde expansion is not performed by realpath.
    Please be aware that this function does NOT always strip a trailing slash!:
    LINUX (tested with PHP 5.2.11):
    ---
    realpath('.')
    : string = "/myhttpdfolder" 
    realpath('./')
    : string = "/myhttpdfolder" 
    realpath('fileadmin')
    : string = "/myhttpdfolder/fileadmin" 
    realpath('fileadmin/')
    : string = "/myhttpdfolder/fileadmin" 
    WINDOWS (tested with PHP 5.2.5):
    ---
    realpath('.')
    : string = "C:\\myhttpdfolder" 
    realpath('./')
    : string = "C:\\myhttpdfolder\\" 
    realpath('fileadmin')
    : string = "C:\\myhttpdfolder\\fileadmin" 
    realpath('fileadmin/')
    : string = "C:\\myhttpdfolder\\fileadmin\\"
    Sometimes you may need to refer to the absolute path of a file in your website instead of a relative path, but the realpath() function returns the path relative to the server's filesystem, not a path relative to your website root directory. 
    For example, realpath() may return something like this:
    /home/yoursite/public_html/dir1/file.ext
    You can't use this in an HTML document, because the web server will not find the file. To do so, you can use:
    <?php
    function htmlpath($relative_path) {
      $realpath=realpath($relative_path);
      $htmlpath=str_replace($_SERVER['DOCUMENT_ROOT'],'',$realpath);
      return $htmlpath;
    }
    echo '<img src="',htmlpath('../../relative/path/to/file.ext'),'" border=1>'; 
    ?>
    It will return something like:
    <img src="/dir1/relative/path/to/file.ext" border=1>
    Here is a small and handy method to calculate the relative path from $from to $to. Note: On Windows it does not work when $from and $to are on different drives.
    <?php
    function relativePath($from, $to, $ps = DIRECTORY_SEPARATOR)
    {
     $arFrom = explode($ps, rtrim($from, $ps));
     $arTo = explode($ps, rtrim($to, $ps));
     while(count($arFrom) && count($arTo) && ($arFrom[0] == $arTo[0]))
     {
      array_shift($arFrom);
      array_shift($arTo);
     }
     return str_pad("", count($arFrom) * 3, '..'.$ps).implode($ps, $arTo);
    }
    ?>
    
    Beware of relative symbolic links like this one (ext4 file system on Ubuntu) :
      vincent@vincent:~/Bureau/dirscan$ readlink sandbox/roulant/voiture/cabriolet/ln-loop-relative
      ../..
    In this case, realpath may return false :
    <?php
    var_dump(realpath('sandbox/roulant/voiture/cabriolet/ln-loop-relative'));
    // => string(44) "/home/vincent/Bureau/dirscan/sandbox/roulant"
    var_dump(realpath('sandbox/roulant/voiture/cabriolet/ln-loop-relative/moto'));
    // => bool(false)
    ?>
    But you can fix it by clearing the realpath cache, this way :
    <?php
    var_dump(realpath('sandbox/roulant/voiture/cabriolet/ln-loop-relative'));
    clearstatcache(true);
    var_dump(realpath('sandbox/roulant/voiture/cabriolet/ln-loop-relative/moto'));
    // => string(49) "/home/vincent/Bureau/dirscan/sandbox/roulant/moto"
    ?>
    
    Be aware that realpath() doesn't work with hidden Windows UNC paths, eg \\servername\share$\folder\blah.txt but other PHP file-functions can access that file fine.
    Note that under Windows, a slash-rooted path will resolve on the local drive, and *not* necessarily C:\.
    For example:
    M:\>php -r "print realpath('/AUTOEXEC.BAT');"
    [prints nothing, because there is no M:\AUTOEXEC.BAT]
    But:
    M:\>C:
    C:\>php -r "print realpath('/AUTOEXEC.BAT');"
    C:\AUTOEXEC.BAT
    Same script, different response depending on current drive.
    I'm inclined to argue that this function *should* use the value of %SystemDrive% as the "slash root" base.
    This function is also nice to test for security-breaches. You can forbid the script to access files below a certain directory to prevent "../../../etc/shadow" and similar attacks:
    <?php
    // declare the basic directory for security reasons
    // Please do NOT attach a "/"-suffix !
    $basedir = '/var/www/cgi-bin/scriptfolder';
    // compare the entered path with the basedir
    $path_parts = pathinfo($_REQUEST['file_to_get']);
    if (realpath($path_parts['dirname']) != $basedir) {
      /* appropriate action against crack-attempt*/
      die ('coding good - h4x1ng bad!');
    }
    ?>
    The url "script.php?file_to_get=../../../etc/shadow" will now result in an error.
    When using realpath (and similar functions) remember that PHP will take in to account open_basedir restrictions. So, if you do something like:
    <?php
    // test.php in httpdocs folder
    $path = realpath(dirname(__FILE__) . '/../application');
    ?>
    where your open_basedir setting is set to the httpdocs folder and tmp, this will return false. You must set it to the level above (or off) for this to work.

    上篇:realpath_cache_size()

    下篇:rename()