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

时间:2009-07-07 08:52:09

标签: php string file

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

如果没有,是否有可用的课程?它对于分割路径或移除部分路径也很有价值。如果你写了一些东西,可以在这里分享你的代码吗?

总是可以使用“/”,我只为Linux编码。

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

23 个答案:

答案 0 :(得分:109)

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'

答案 1 :(得分:47)

  

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

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/');

:○)

答案 2 :(得分:17)

@deceze的函数在尝试加入以Unix绝对路径开头的路径时不会保持领先/例如,例如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);
}

答案 3 :(得分:14)

我的看法:

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));
}

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

答案 4 :(得分:5)

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

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

这当然与Python版本不太一样,但在很多情况下可能还不错。

答案 5 :(得分: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

答案 6 :(得分:2)

获取路径的一部分,您可以使用pathinfo http://nz2.php.net/manual/en/function.pathinfo.php

加入@deceze的回复看起来很好

答案 7 :(得分:2)

攻击这个的另一种方式:

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

答案 8 :(得分:2)

下面的解决方案使用@RiccardoGalli提出的逻辑,但是改进后可以利用// Set of Doubles let set: Set<Double> = [1, 2.0, 3] let setAsString: String = set.description let setStringAsData = setAsString.data(using: String.Encoding.utf16) let setBack: Set<Double> = try! JSONDecoder().decode(Set<Double>.self, from: setStringAsData!) 常量,如@Qix和@FélixSaparelli建议的那样,更重要的是,修剪每个给定的元素以避免在最终路径中出现仅限空格的文件夹名称(在我的情况下这是一项要求)。

关于DIRECTORY_SEPARATOR模式中目录分隔符的转义,正如您所看到的,我使用了preg_replace()函数来完成工作。
此外,我只会替换多个分隔符(RegExp量词preg_quote())。

{2,}

答案 9 :(得分:1)

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

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"

结果:

    import android.net.Uri;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.support.v7.widget.DividerItemDecoration;
    import android.support.v7.widget.RecyclerView;

    import java.util.ArrayList;
    import java.util.List;



    app:layoutManager="android.support.v7.widget.LinearLayoutManager">

    public class MainActivity extends AppCompatActivity implements 
    Dictionary.OnFragmentInteractionListener {

    RecyclerView recyclerView;
    RecyclerAdapter recyclerAdapter;

    List<String> moviesList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    moviesList = new ArrayList<>();

    recyclerView = findViewById(R.id.recyclerView);
    recyclerAdapter = new RecyclerAdapter(moviesList);

//        recyclerView.setLayoutManager(new LinearLayoutManager(this));

    recyclerView.setAdapter(recyclerAdapter);

    DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(this, 
    DividerItemDecoration.VERTICAL);
    recyclerView.addItemDecoration(dividerItemDecoration);


    moviesList.add("IronMan");
    moviesList.add("The Incredible Hulk");
    moviesList.add("Iron Man 2");
    moviesList.add("Thor");
    moviesList.add("Captain America: The First Avenger");
    moviesList.add("The Avengers");
    moviesList.add("Iron Man 3");
    moviesList.add("Thor: The Dark World");
    moviesList.add("Captain America: The Winter Soldier");
    moviesList.add("Guardians of the Galaxy");
    moviesList.add("Avengers: Age of Ultron");
    moviesList.add("Ant-Man");
    moviesList.add("Captain America: Civil War");
    moviesList.add("Doctor Strange");
    moviesList.add("Guardians of the Galaxy Vol. 2");
    moviesList.add("Spider-Man: Homecoming");
    moviesList.add("Thor: Ragnarok");
    moviesList.add("Black Panther");
    moviesList.add("Avengers: Infinity War");
    moviesList.add("Ant-Man and the Wasp");
    moviesList.add("Captain Marvel");
    moviesList.add("Avengers: Endgame");
    moviesList.add("Spider-Man: Far From Home");
    }

    @Override
    public void onFragmentInteraction(Uri uri) {

    }
    }




import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import java.util.List;

public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ViewHolder>  {

String name="";

private static final String TAG = "RecyclerAdapter";
List<String> moviesList;

public RecyclerAdapter(List<String> moviesList) {
    this.moviesList = moviesList;
    }

@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
    View view = layoutInflater.inflate(R.layout.row_item, parent, false);
    ViewHolder viewHolder = new ViewHolder(view);


    return viewHolder;
    }

@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
    holder.rowCountTextView.setText(String.valueOf(position));
    holder.textView.setText(moviesList.get(position));
    }

@Override
public int getItemCount() {
    return moviesList.size();
    }

class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

    ImageView imageView;
    TextView textView, rowCountTextView;

    public ViewHolder(@NonNull View itemView) {
        super(itemView);

        imageView = itemView.findViewById(R.id.imageView);
        textView = itemView.findViewById(R.id.textView);
        rowCountTextView = itemView.findViewById(R.id.rowCountTextView);

        itemView.setOnClickListener(this);

        itemView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View view) {
                moviesList.remove(getAdapterPosition());
                notifyItemRemoved(getAdapterPosition());
                return true;
            }
        });

    }

    @Override
    public void onClick(View view) {
        name=moviesList.get(getAdapterPosition()).toString();
        AppCompatActivity activity=(AppCompatActivity) view.getContext();
        Fragment myfragment = new Dictionary();
        activity.getSupportFragmentManager().beginTransaction().replace(R.id.fragmentid ,myfragment ).addToBackStack(null).commit();

       // Toast.makeText(view.getContext(),name, Toast.LENGTH_SHORT).show();


      }
  }
 }





     import android.content.Context;
    import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;



public class Dictionary extends Fragment {
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";

// TODO: Rename and change types of parameters
private String mParam1;
private String mParam2;

private OnFragmentInteractionListener mListener;

public Dictionary() {
    // Required empty public constructor
         }

/**
 * Use this factory method to create a new instance of
 * this fragment using the provided parameters.
 *
 * @param param1 Parameter 1.
 * @param param2 Parameter 2.
 * @return A new instance of fragment Dictionary.
 */
// TODO: Rename and change types and number of parameters
public static Dictionary newInstance(String param1, String param2) {
    Dictionary fragment = new Dictionary();
    Bundle args = new Bundle();
    args.putString(ARG_PARAM1, param1);
    args.putString(ARG_PARAM2, param2);
    fragment.setArguments(args);
    return fragment;
}

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (getArguments() != null) {
        mParam1 = getArguments().getString(ARG_PARAM1);
        mParam2 = getArguments().getString(ARG_PARAM2);
    }
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    return inflater.inflate(R.layout.fragment_dictionary, container, false);
}

// TODO: Rename method, update argument and hook method into UI event
public void onButtonPressed(Uri uri) {
    if (mListener != null) {
        mListener.onFragmentInteraction(uri);
    }
}

@Override
public void onAttach(Context context) {
    super.onAttach(context);
    if (context instanceof OnFragmentInteractionListener) {
        mListener = (OnFragmentInteractionListener) context;
    } else {
        throw new RuntimeException(context.toString()
                + " must implement OnFragmentInteractionListener");
    }
}

@Override
public void onDetach() {
    super.onDetach();
    mListener = null;
}

public interface OnFragmentInteractionListener {
    // TODO: Update argument type and name
    void onFragmentInteraction(Uri uri);
    }
}

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layoutManager="android.support.v7.widget.LinearLayoutManager">

</android.support.v7.widget.RecyclerView>
</RelativeLayout>




<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/fragmentid"
tools:context=".Dictionary">

<TextView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:text="@string/hello_blank_fragment" />


</FrameLayout>



<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"

android:layout_width="match_parent"
android:layout_height="wrap_content">

<ImageView
    android:id="@+id/imageView"
    android:layout_width="48dp"
    android:layout_height="48dp"
    android:layout_marginStart="16dp"
    android:layout_marginTop="16dp"
    android:layout_marginBottom="16dp"

    app:srcCompat="@mipmap/ic_launcher" />

 <TextView
    android:id="@+id/rowCountTextView"
    android:layout_width="50dp"
    android:layout_height="wrap_content"


    android:layout_alignTop="@+id/imageView"
    android:layout_alignParentEnd="true"
    android:layout_marginTop="9dp"
    android:layout_marginEnd="120dp"
    android:text="TextView"
    android:textAppearance="@style/TextAppearance.AppCompat.Small" />

 <TextView
    android:id="@+id/textView"
    android:layout_width="50dp"
    android:layout_height="wrap_content"


    android:layout_alignTop="@+id/imageView"
    android:layout_alignParentEnd="true"
    android:layout_marginTop="-4dp"
    android:layout_marginEnd="207dp"
    android:text="TextView"
    android:textAppearance="@style/TextAppearance.AppCompat.Large" />

</RelativeLayout>

答案 10 :(得分:1)

找到最佳解决方案:

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

注意:从user89021的评论中复制

答案 11 :(得分:1)

这似乎工作得很好,看起来很合理。

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

答案 12 :(得分: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;
}

答案 13 :(得分:0)

从里卡多加利的伟大答案中得到了一些改进,以避免杀死协议前缀。

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

例如:

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

结果(保持协议)

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

而不是(杀死协议)

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

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

答案 14 :(得分:0)

这是一个行为类似于Node's 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'

答案 15 :(得分:0)

我爱Riccardo's answer,我认为这是最好的答案。

我正在使用它来连接 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);
}

答案 16 :(得分:0)

优雅的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

答案 17 :(得分:0)

基于the answer by 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);

答案 18 :(得分: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

答案 19 :(得分: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;
        }

答案 20 :(得分: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);
}

答案 21 :(得分: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);
    }
  }
}

答案 22 :(得分:-6)

我喜欢提出几种解决方案。但那些确实取代所有&#39; / +&#39;进入&#39; /&#39; (正则表达式)忘记了python中的os.path.join()可以处理这种连接:

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

结果:&#39; http://example.com/parent/path/subdir/file.html&#39;