¿Cómo unir cadenas de ruta del sistema de archivos en PHP?


¿Hay una función incorporada en PHP para unir inteligentemente cadenas de ruta? La función, dada "abc / de/" y "/fg/x.php " como argumentos, debe volver "abc/de/fg/x.php"; el mismo resultado debe ser dado usando " abc / de "y" fg/x.php " como argumentos para esa función.

Si no, hay una clase disponible? También podría ser útil para dividir rutas o eliminar partes de ellas. Si has escrito algo, ¿puedes compartir tu código aquí?

Está bien usar siempre"/", yo soy codificación solo para Linux.

En Python hay os.path.join(), lo cual es genial.

Author: kiamlaluno, 2009-07-07

15 answers

Ya que esta parece ser una pregunta popular y los comentarios se están llenando con "sugerencias de características" o "informes de errores"... Todo lo que hace este fragmento de código es unir dos cadenas con una barra sin duplicar barras entre ellas. Eso es todo. Ni más, ni menos. No evalúa las rutas reales en el disco duro ni mantiene la barra diagonal inicial (agregue eso de nuevo si es necesario, al menos puede estar seguro de que este código siempre devuelve una cadena sin comenzar recortar).

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

El resultado final siempre será un camino sin barras al principio o al final y sin barras dobles dentro. Siéntase libre de hacer una función de eso.

EDITAR: Aquí hay un buen envoltorio de función flexible para el fragmento anterior. Puede pasar tantos fragmentos de ruta como desee, ya sea como matriz o argumentos separados:

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)

 44
Author: deceze,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-24 11:13:45
function join_paths() {
    $paths = array();

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

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

Mi solución es más simple y más similar a la forma en que Python os.camino.join works

Considere estos casos de prueba

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'
 96
Author: Riccardo Galli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-26 11:06:04

La función de@deceze no mantiene el principio / cuando intenta unirse a una ruta que comienza con una ruta absoluta de Unix, por ejemplo, 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
Author: David Miller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-08 01:05:10

Mi opinión:

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

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

Habría usado una función anónima para trimds, pero las versiones anteriores de PHP no lo soportan.

Ejemplo:

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

Actualizado Abril 2013 Marzo 2014 Mayo 2018:

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

Este corregirá las barras para que coincidan con su sistema operativo, no eliminará una barra diagonal y limpiará varias barras en una fila.

 11
Author: mpen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-24 19:54:01

Una alternativa es usar implode() y 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
 4
Author: Chris J,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-08 01:07:45

Si sabe que el archivo/directorio existe , puede agregar barras adicionales (que pueden ser innecesarias), luego llame a realpath, es decir,

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

Esto, por supuesto, no es lo mismo que la versión de Python, pero en muchos casos puede ser lo suficientemente bueno.

 3
Author: George Lund,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-27 16:08:54

Para obtener partes de rutas puede usar pathinfo http://nz2.php.net/manual/en/function.pathinfo.php

Para unirse a la respuesta de @deceze se ve bien

 2
Author: bumperbox,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-07 09:17:54

Una forma diferente de atacar a este:

function joinPaths() {
  $paths = array_filter(func_get_args());
  return preg_replace('#/{2,}#', '/', implode('/', $paths));
}
 2
Author: stompydan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-08 01:09:08

Esta es una versión corregida de la función publicada por deceze. Sin este cambio, joinPaths ( " , ' foo.jpg') se convierte en ' / 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
Author: Dwayne,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-11 01:39:43

Esto parece ser un trabajo bastante bien, y me parece razonablemente limpio.

private function JoinPaths() {
  $slash = DIRECTORY_SEPARATOR;
  $sections = preg_split(
          "@[/\\\\]@",
          implode('/', func_get_args()),
          null,
          PREG_SPLIT_NO_EMPTY);
  return implode($slash, $sections);
}
 1
Author: Kenny Hung,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-14 21:43:42

Mejor solución encontrada:

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

NOTA: Copiado del comentario por user89021

 1
Author: Basil Musa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-04 23:08:08

La solución a continuación utiliza la lógica propuesta por @RiccardoGalli, pero se mejora para aprovechar la constante DIRECTORY_SEPARATOR, como sugirieron @Qix y @FélixSaparelli, y, más importante, para recortar cada elemento dado para evitar que aparezcan nombres de carpetas solo de espacio en la ruta final (era un requisito en mi caso).

Con respecto al escape del separador de directorios dentro del patrón preg_replace(), como puede ver, usé la función preg_quote() que hace el trabajo bien.
Además, yo reemplazaría solo separadores múltiples (cuantificador de expresiones regulares {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
    );
}
 1
Author: Emanuele Del Grande,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-24 22:32:02

Aquí hay una función que se comporta como el nodo 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);
}

Casos de prueba:

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
Author: mpen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-06 23:20:09

De la gran respuesta de Ricardo Galli, un poco de mejora para evitar matar el prefijo del protocolo.

La idea es probar la presencia de un protocolo en un argumento, y mantenerlo en el resultado. ADVERTENCIA: ¡esta es una implementación ingenua!

Por ejemplo:

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

Resultados para (mantener el protocolo)

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

En lugar de (matar protocolo)

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

Pero http://codepad.org/hzpWmpzk necesita una mejor habilidad para escribir código.

 0
Author: nicolallias,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-13 14:44:57

Me gustaron varias soluciones presentadas. Pero aquellos que reemplazan todo '/ + ' en ' / ' (expresiones regulares) están olvidando ese sistema operativo.camino.join() de python puede manejar este tipo de join:

os.path.join('http://example.com/parent/path', 'subdir/file.html')

Resultado: 'http://example.com/parent/path/subdir/file.html'

 -6
Author: Roger Demetrescu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-21 20:09:21