如何在PHP中加入文件系统路径字符串?


76

PHP中是否有内置函数来智能地连接路径字符串?给定abc/de//fg/x.php作为参数的函数应返回abc/de/fg/x.php; 应该使用abc/defg/x.php作为该函数的参数给出相同的结果。

如果没有,是否有可用的课程?这对于拆分路径或删除其中的一部分可能也很有价值。如果您写过什么,可以在这里分享您的代码吗?

可以始终使用/,我只为Linux编码。

在Python中有os.path.join,这很棒。


5
哇,我刚从Google来到这里。不敢相信PHP就是这样。
德米特里·明科夫斯基2013年

4
请注意,您的示例会产生误导,因为os.path.join('some/relative/path, '/an/absolute/path')它将始终返回/an/absolute/path。因此,您正在寻找os.path.join替代品(然后修复您的示例)或与其接近的替代品,除了将第二(或第n)个绝对路径视为相对路径。

2
@Tibo和他的评论的支持者:不,该示例完全不会引起误解,除非一个人忽略了问题的要点,并坚持过分强调Python参考,这是定位的一个简短说明。关于它想要什么,这个问题很清楚,这完全是os.path.join。阅读:“给定abc/de//fg/x.php作为参数的函数应返回abc/de/fg/x.php”。
Sz。

令人作呕的是,PHP没有为此内置函数
Silidrone

Answers:


54

由于这似乎是一个很受欢迎的问题,并且注释中充斥着“功能建议”或“错误报告” ...此代码段所做的只是将两个带有斜杠的字符串连接在一起,而不必在它们之间重复斜杠。就这样。不多不少。它不评估硬盘上的实际路径,也不实际保留开始的斜杠(如果需要,可以将其添加回去,至少可以确保此代码始终返回字符串而不开始斜杠)。

join('/', array(trim("abc/de/", '/'), trim("/fg/x.php", '/')));

最终结果将始终是在开头或结尾没有斜杠且在其中没有双斜杠的路径。随意发挥作用。

编辑:这是上面代码段的一个很好的灵活函数包装器。您可以根据需要传递任意数量的路径摘要,可以是数组或单独的参数:

function joinPaths() {
    $args = func_get_args();
    $paths = array();
    foreach ($args as $arg) {
        $paths = array_merge($paths, (array)$arg);
    }

    $paths = array_map(create_function('$p', 'return trim($p, "/");'), $paths);
    $paths = array_filter($paths);
    return join('/', $paths);
}

echo joinPaths(array('my/path', 'is', '/an/array'));
//or
echo joinPaths('my/paths/', '/are/', 'a/r/g/u/m/e/n/t/s/');

:o)


5
函数pj($ a,$ b){return rtrim($ a,'/')。'/'。ltrim($ b,'/'); }
user89021

2
这并不总是如所描述的那样工作。joinPaths('','foo.jpg')变为'/foo.jpg'。我的php文件管理器开始将用户更新的文件写入文件系统的根目录后,我注意到了这一点!更正的版本应删除所有为空字符串的路径。
EricP'1

35
应该使用DIRECTORY_SEPARATOR而不是'/'吗?
戴夫2012年

2
@fe_与问题所要的功能完全不同。
deceze

3
我不同意这个人的明确通知,他曾经使用过python'sos.path.join给出的结果,该结果给出了这个结果,并且他觉得很棒。因此,我不认为这是另一个功能。就像join('/a/b','../c')返回时一样,/a/c不需要任何外部规范化。
fe_lix_

130
function join_paths() {
    $paths = array();

    foreach (func_get_args() as $arg) {
        if ($arg !== '') { $paths[] = $arg; }
    }

    return preg_replace('#/+#','/',join('/', $paths));
}

我的解决方案更简单,更类似于Python os.path.join的工作方式

考虑这些测试案例

array               my version    @deceze      @david_miller    @mark

['','']             ''            ''           '/'              '/'
['','/']            '/'           ''           '/'              '/'
['/','a']           '/a'          'a'          '//a'            '/a'
['/','/a']          '/a'          'a'          '//a'            '//a'
['abc','def']       'abc/def'     'abc/def'    'abc/def'        'abc/def'
['abc','/def']      'abc/def'     'abc/def'    'abc/def'        'abc//def'
['/abc','def']      '/abc/def'    'abc/def'    '/abc/def'       '/abc/def'
['','foo.jpg']      'foo.jpg'     'foo.jpg'    '/foo.jpg'       '/foo.jpg'
['dir','0','a.jpg'] 'dir/0/a.jpg' 'dir/a.jpg'  'dir/0/a.jpg'    'dir/0/a.txt'

5
这是最好的答案,因为它与问题最匹配-它是最接近问题的,os.path.join并且可以智能地联接路径字符串。可以通过添加的“引用”实现os.path.join并指示违反规则的OP的具体细节来改进答案(测试用例['abc','/def']是错误的wrt os.path.join,但根据问题正确)。

@qix为什么?Windows理解正斜杠就可以了
Riccardo Galli 2014年

4
@qix看,我确实理解您的意思,通常我会同意您的观点,但是实际上,PHP不会很快在不使用斜杠作为路径分隔符的平台上运行,而要使用常量preg_replace()会变成一团糟(您需要在正则表达式中转义路径分隔符),所以我选择了这种折衷方案。
Riccardo Galli 2014年

4
所以您偷懒是因为您懒惰,即使PHP中常量的字符串内插几乎是免费的?sk
Qix-蒙尼卡(Monica)

3
需要注意的是,我的团队刚刚发现了一种/分隔符不起作用的情况(使用msys git shell在Windows Server 2012上安装了旧的PHP5.3.4)。
费利克斯Saparelli

17

尝试加入以Unix绝对路径开头的路径时,例如@deceze的功能不会保留前导/ joinPaths('/var/www', '/vhosts/site');

function unix_path() {
  $args = func_get_args();
  $paths = array();

  foreach($args as $arg) {
    $paths = array_merge($paths, (array)$arg);
  }

  foreach($paths as &$path) {
    $path = trim($path, '/');
  }

  if (substr($args[0], 0, 1) == '/') {
    $paths[0] = '/' . $paths[0];
  }

  return join('/', $paths);
}

16

我的看法:

function trimds($s) {
    return rtrim($s,DIRECTORY_SEPARATOR);
}

function joinpaths() {
    return implode(DIRECTORY_SEPARATOR, array_map('trimds', func_get_args()));
}

我本来可以使用匿名函数 trimds,但是旧版本的PHP不支持它。

例:

join_paths('a','\\b','/c','d/','/e/','f.jpg'); // a\b\c\d\e\f.jpg (on Windows)

更新 2013年4月 2014年3月 2018年5月

function join_paths(...$paths) {
    return preg_replace('~[/\\\\]+~', DIRECTORY_SEPARATOR, implode(DIRECTORY_SEPARATOR, $paths));
}

这将纠正任何斜杠以匹配您的操作系统,不会删除前导斜杠,并且连续清理和清除多个斜杠。


13
它总是会创建一条绝对路径,但至少有人提到DIRECTORY_SEPARATOR ...
Karoly Horvath

7

如果知道文件/目录存在,则可以添加额外的斜杠(可能是不必要的),然后调用realpath,即

realpath(join('/', $parts));

当然,这与Python版本并不完全相同,但是在许多情况下可能已经足够了。


4

一种替代方法是使用implode()explode()

$a = '/a/bc/def/';
$b = '/q/rs/tuv/path.xml';

$path = implode('/',array_filter(explode('/', $a . $b)));

echo $path;  // -> a/bc/def/q/rs/tuv/path.xml

尝试url /offset/0/limit/1

3

下面的解决方案使用@RiccardoGalli提出的逻辑,但经过改进以利用DIRECTORY_SEPARATOR常量本身,如@Qix和@FélixSaparelli所建议的那样,更重要的是,修剪每个给定的元素以避免在最终的目录中出现仅空格的文件夹名称路径(在我的情况下这是必需条件)。

关于preg_replace()模式中目录分隔符的转义,如您所见,我使用了preg_quote()功能良好的函数。
此外,我将只替换多个分隔符(RegExp量化器{2,})。

// PHP 7.+
function paths_join(string ...$parts): string {
    $parts = array_map('trim', $parts);
    $path = [];

    foreach ($parts as $part) {
        if ($part !== '') {
            $path[] = $part;
        }
    }

    $path = implode(DIRECTORY_SEPARATOR, $path);

    return preg_replace(
        '#' . preg_quote(DIRECTORY_SEPARATOR) . '{2,}#',
        DIRECTORY_SEPARATOR,
        $path
    );
}


2

一种不同的攻击方式:

function joinPaths() {
  $paths = array_filter(func_get_args());
  return preg_replace('#/{2,}#', '/', implode('/', $paths));
}

1

这是deceze发布的功能的更正版本。没有此更改,joinPaths('','foo.jpg')变为'/foo.jpg'

function joinPaths() {
    $args = func_get_args();
    $paths = array();
    foreach ($args as $arg)
        $paths = array_merge($paths, (array)$arg);

    $paths2 = array();
    foreach ($paths as $i=>$path)
    {   $path = trim($path, '/');
        if (strlen($path))
            $paths2[]= $path;
    }
    $result = join('/', $paths2); // If first element of old path was absolute, make this one absolute also
    if (strlen($paths[0]) && substr($paths[0], 0, 1) == '/')
        return '/'.$result;
    return $result;
}

1

这似乎工作得很好,并且对我来说看起来很整洁。

private function JoinPaths() {
  $slash = DIRECTORY_SEPARATOR;
  $sections = preg_split(
          "@[/\\\\]@",
          implode('/', func_get_args()),
          null,
          PREG_SPLIT_NO_EMPTY);
  return implode($slash, $sections);
}

1

找到最佳解决方案:

function joinPaths($leftHandSide, $rightHandSide) { 
    return rtrim($leftHandSide, '/') .'/'. ltrim($rightHandSide, '/'); 
}

注意:从user89021的注释中复制


1

与操作系统无关的版本,基于mpen的答案,但封装为单个函数,并可以选择添加尾随路径分隔符。

function joinPathParts($parts, $trailingSeparator = false){
    return implode(
        DIRECTORY_SEPARATOR, 
        array_map(
            function($s){
                return rtrim($s,DIRECTORY_SEPARATOR);
            }, 
            $parts)
        )
        .($trailingSeparator ? DIRECTORY_SEPARATOR : '');
}

或为您的单线恋人:

function joinPathParts($parts, $trailingSeparator = false){
    return implode(DIRECTORY_SEPARATOR, array_map(function($s){return rtrim($s,DIRECTORY_SEPARATOR);}, $parts)).($trailingSeparator ? DIRECTORY_SEPARATOR : '');
}

只需使用一系列路径部分来调用它:

// No trailing separator - ex. C:\www\logs\myscript.txt
$logFile = joinPathParts([getcwd(), 'logs', 'myscript.txt']);

// Trailing separator - ex. C:\www\download\images\user1234\
$dir = joinPathParts([getcwd(), 'download', 'images', 'user1234'], true);

1

优雅的Python启发式PHP一线式加入路径。

此代码不使用不必要的数组。

多平台

function os_path_join(...$parts) {
  return preg_replace('#'.DIRECTORY_SEPARATOR.'+#', DIRECTORY_SEPARATOR, implode(DIRECTORY_SEPARATOR, array_filter($parts)));
}

基于Unix的系统

function os_path_join(...$parts) {
  return preg_replace('#/+#', '/', implode('/', array_filter($parts)));
}

没有REST参数的基于Unix的系统(不遵循明确的PEP8原理):

function os_path_join() {
  return preg_replace('#/+#', '/', implode('/', array_filter(func_get_args())));
}

用法

$path = os_path_join("", "/", "mydir/", "/here/");

奖励:如果您想真正遵循Python os.path.join()。第一个参数是必需的:

function os_path_join($path=null, ...$paths) {
  if (!is_null($path)) {
    throw new Exception("TypeError: join() missing 1 required positional argument: 'path'", 1);
  }
  $path = rtrim($path, DIRECTORY_SEPARATOR);
  foreach ($paths as $key => $current_path) {
    $paths[$key] = $paths[$key] = trim($current_path, DIRECTORY_SEPARATOR);
  }
  return implode(DIRECTORY_SEPARATOR, array_merge([$path], array_filter($paths)));
}

如果需要,请检查os.path.join()源:https : //github.com/python/cpython/blob/master/Lib/ntpath.py

警告:此解决方案不适用于网址。


1

作为一个有趣的项目,我创建了另一个解决方案。对于所有操作系统应通用:

<?php

function join_paths(...$parts) {
    if (sizeof($parts) === 0) return '';
    $prefix = ($parts[0] === DIRECTORY_SEPARATOR) ? DIRECTORY_SEPARATOR : '';
    $processed = array_filter(array_map(function ($part) {
        return rtrim($part, DIRECTORY_SEPARATOR);
    }, $parts), function ($part) {
        return !empty($part);
    });
    return $prefix . implode(DIRECTORY_SEPARATOR, $processed);
}

// relative paths
var_dump(join_paths('hello/', 'world'));
var_dump(join_paths('hello', 'world'));
var_dump(join_paths('hello', '', 'world'));
var_dump(join_paths('', 'hello/world'));
echo "\n";

// absolute paths
var_dump(join_paths('/hello/', 'world'));
var_dump(join_paths('/hello', 'world'));
var_dump(join_paths('/hello/', '', 'world'));
var_dump(join_paths('/hello', '', 'world'));
var_dump(join_paths('', '/hello/world'));
var_dump(join_paths('/', 'hello/world'));

结果:

string(11) "hello/world"
string(11) "hello/world"
string(11) "hello/world"
string(11) "hello/world"

string(12) "/hello/world"
string(12) "/hello/world"
string(12) "/hello/world"
string(12) "/hello/world"
string(12) "/hello/world"
string(12) "/hello/world"

+1非常感谢@Koala Yeung。您的解决方案与高于v7.2的PHP版本兼容。接受的答案中的代码使用create_function(),该版本在7.2以上的版本中不推荐使用-在2013年,我们只有PHP v5.5。
JackLeEmmerdeur

0

这是一个行为类似于Node的path.resolve函数:

function resolve_path() {
    $working_dir = getcwd();
    foreach(func_get_args() as $p) {
        if($p === null || $p === '') continue;
        elseif($p[0] === '/') $working_dir = $p;
        else $working_dir .= "/$p";
    }
    $working_dir = preg_replace('~/{2,}~','/', $working_dir);
    if($working_dir === '/') return '/';
    $out = [];
    foreach(explode('/',rtrim($working_dir,'/')) as $p) {
        if($p === '.') continue;
        if($p === '..') array_pop($out);
        else $out[] = $p;
    }
    return implode('/',$out);
}

测试用例:

resolve_path('/foo/bar','./baz')         # /foo/bar/baz
resolve_path('/foo/bar','/tmp/file/')    # /tmp/file
resolve_path('/foo/bar','/tmp','file')   # /tmp/file
resolve_path('/foo//bar/../baz')         # /foo/baz
resolve_path('/','foo')                  # /foo
resolve_path('/','foo','/')              # /
resolve_path('wwwroot', 'static_files/png/', '../gif/image.gif') 
                                  # __DIR__.'/wwwroot/static_files/gif/image.gif'

0

从里卡多·加里(Ricardo Galli)的出色回答中可以避免避免取消协议前缀的一些改进。

这个想法是在一个参数中测试协议的存在,并将其保留到结果中。警告:这是一个幼稚的实现!

例如:

array("http://domain.de","/a","/b/")

结果到(保存协议)

"http://domain.de/a/b/"

而不是(杀死协议)

"http:/domain.de/a/b/"

但是http://codepad.org/hzpWmpzk需要更好的代码编写技能。


0

我喜欢里卡多的答案,我认为这是最好的答案。

我正在使用它来连接URL构建中的路径,但是做了一个小的改动以处理协议的双斜杠:

function joinPath () {
    $paths = array();

    foreach (func_get_args() as $arg) {
        if ($arg !== '') { $paths[] = $arg; }
    }

    // Replace the slash with DIRECTORY_SEPARATOR
    $paths = preg_replace('#/+#', '/', join('/', $paths));
    return preg_replace('#:/#', '://', $paths);
}

0
function path_combine($paths) {
  for ($i = 0; $i < count($paths); ++$i) {
    $paths[$i] = trim($paths[$i]);
  }

  $dirty_paths = explode(DIRECTORY_SEPARATOR, join(DIRECTORY_SEPARATOR, $paths));
  for ($i = 0; $i < count($dirty_paths); ++$i) {
    $dirty_paths[$i] = trim($dirty_paths[$i]);
  }

  $unslashed_paths = array();

  for ($i = 0; $i < count($dirty_paths); ++$i) {
    $path = $dirty_paths[$i];
    if (strlen($path) == 0) continue;
    array_push($unslashed_paths, $path);
  }

  $first_not_empty_index = 0;
  while(strlen($paths[$first_not_empty_index]) == 0) {
    ++$first_not_empty_index;
  }
  $starts_with_slash = $paths[$first_not_empty_index][0] == DIRECTORY_SEPARATOR;

  return $starts_with_slash
    ? DIRECTORY_SEPARATOR . join(DIRECTORY_SEPARATOR, $unslashed_paths)
    : join(DIRECTORY_SEPARATOR, $unslashed_paths);
}

用法示例:

$test = path_combine([' ', '/cosecheamo', 'pizze', '///// 4formaggi', 'GORGONZOLA']);
echo $test;

将输出:

/cosecheamo/pizze/4formaggi/GORGONZOLA

0

这是我的解决方案:

function joinPath(): string {

        $path = '';
        foreach (func_get_args() as $numArg => $arg) {

            $arg = trim($arg);

            $firstChar = substr($arg, 0, 1);
            $lastChar = substr($arg, -1);

            if ($numArg != 0 && $firstChar != '/') {
                $arg = '/'.$arg;
                }

            # Eliminamos el slash del final
            if ($lastChar == '/') {
                $arg = rtrim($arg, '/');
                }

            $path .= $arg;
            }

        return $path;
        }

0

嗯,大多数似乎有点复杂。邓诺,这是我的看法:

// Takes any amount of arguments, joins them, then replaces double slashes
function join_urls() {
   $parts = func_get_args();
   $url_part = implode("/", $parts);
   return preg_replace('/\/{1,}/', '/', $url_part);
}

0

对于想要使用Windows反斜杠和Linux正斜杠的联接功能的用户。

用法:

<?php
use App\Util\Paths
echo Paths::join('a','b'); //Prints 'a/b' on *nix, or 'a\\b' on Windows

类文件:

<?php
namespace App\Util;

class Paths
{
  public static function join_with_separator($separator, $paths) {
    $slash_delimited_path = preg_replace('#\\\\#','/', join('/', $paths));
    $duplicates_cleaned_path = preg_replace('#/+#', $separator, $slash_delimited_path);
    return $duplicates_cleaned_path;
  }

  public static function join() {
    $paths = array();

    foreach (func_get_args() as $arg) {
      if ($arg !== '') { $paths[] = $arg; }
    }
    return Paths::join_with_separator(DIRECTORY_SEPARATOR, $paths);
  }
}

这是测试功能:

<?php

namespace Tests\Unit;

use PHPUnit\Framework\TestCase;
use App\Util\Paths;

class PathsTest extends TestCase
{
  public function testWindowsPaths()
  {
    $TEST_INPUTS = [
      [],
      ['a'],
      ['a','b'],
      ['C:\\','blah.txt'],
      ['C:\\subdir','blah.txt'],
      ['C:\\subdir\\','blah.txt'],
      ['C:\\subdir','nested','1/2','blah.txt'],
    ];
    $EXPECTED_OUTPUTS = [
      '',
      'a',
      'a\\b',
      'C:\\blah.txt',
      'C:\\subdir\\blah.txt',
      'C:\\subdir\\blah.txt',
      'C:\\subdir\\nested\\1\\2\\blah.txt',
    ];
    for ($i = 0; $i < count($TEST_INPUTS); $i++) {
      $actualPath = Paths::join_with_separator('\\', $TEST_INPUTS[$i]);
      $expectedPath = $EXPECTED_OUTPUTS[$i];
      $this->assertEquals($expectedPath, $actualPath);
    }
  }
  public function testNixPaths()
  {
    $TEST_INPUTS = [
      [],
      ['a'],
      ['a','b'],
      ['/home','blah.txt'],
      ['/home/username','blah.txt'],
      ['/home/username/','blah.txt'],
      ['/home/subdir','nested','1\\2','blah.txt'],
    ];
    $EXPECTED_OUTPUTS = [
      '',
      'a',
      'a/b',
      '/home/blah.txt',
      '/home/username/blah.txt',
      '/home/username/blah.txt',
      '/home/subdir/nested/1/2/blah.txt',
    ];
    for ($i = 0; $i < count($TEST_INPUTS); $i++) {
      $actualPath = Paths::join_with_separator('/', $TEST_INPUTS[$i]);
      $expectedPath = $EXPECTED_OUTPUTS[$i];
      $this->assertEquals($expectedPath, $actualPath);
    }
  }
}

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.