Получить имя файла из пути - PullRequest
69 голосов
/ 15 декабря 2011

Какой самый простой способ получить имя файла по пути?

string filename = "C:\\MyDirectory\\MyFile.bat"

В этом примере я должен получить «MyFile».без расширения.

Ответы [ 19 ]

49 голосов
/ 15 декабря 2011

Возможное решение:

string filename = "C:\\MyDirectory\\MyFile.bat";

// Remove directory if present.
// Do this before extension removal incase directory has a period character.
const size_t last_slash_idx = filename.find_last_of("\\/");
if (std::string::npos != last_slash_idx)
{
    filename.erase(0, last_slash_idx + 1);
}

// Remove extension if present.
const size_t period_idx = filename.rfind('.');
if (std::string::npos != period_idx)
{
    filename.erase(period_idx);
}
38 голосов
/ 15 декабря 2011

Самое простое решение - использовать что-то вроде boost::filesystem.Если по какой-либо причине это не вариант ...

Для правильной работы потребуется некоторый системно-зависимый код: в Windows '\\' или '/' может быть разделителем пути;под Unix работает только '/', а под другими системами кто знает.Очевидное решение будет выглядеть примерно так:

std::string
basename( std::string const& pathname )
{
    return std::string( 
        std::find_if( pathname.rbegin(), pathname.rend(),
                      MatchPathSeparator() ).base(),
        pathname.end() );
}

, где MatchPathSeparator определяется в системно-зависимом заголовке как:

struct MatchPathSeparator
{
    bool operator()( char ch ) const
    {
        return ch == '/';
    }
};

для Unix или:

struct MatchPathSeparator
{
    bool operator()( char ch ) const
    {
        return ch == '\\' || ch == '/';
    }
};

для Windows (или что-то еще другое для какой-то другой неизвестной системы).

РЕДАКТИРОВАТЬ: я пропустил тот факт, что он также хотел подавить расширение.Для этого, больше того же самого:

std::string
removeExtension( std::string const& filename )
{
    std::string::const_reverse_iterator
                        pivot
            = std::find( filename.rbegin(), filename.rend(), '.' );
    return pivot == filename.rend()
        ? filename
        : std::string( filename.begin(), pivot.base() - 1 );
}

Код немного более сложен, потому что в этом случае основа обратного итератора находится не на той стороне, где мы хотим вырезать.(Помните, что основа обратного итератора находится позади символа, на который указывает итератор.) И даже это немного сомнительно: мне не нравится тот факт, что он может возвращать пустую строку, например.(Если единственным '.' является первый символ имени файла, я бы сказал, что вы должны вернуть полное имя файла. Для этого потребуется немного дополнительного кода, чтобы поймать особый случай.)}

36 голосов
/ 24 июня 2014

Задача довольно проста, поскольку базовое имя файла - это просто часть строки, начинающаяся с последнего разделителя для папок:

std::string base_filename = path.substr(path.find_last_of("/\\") + 1)

Если расширение также будет удалено, единственное, что нужно сделать, это найти последний . и взять substr до этой точки

std::string::size_type const p(base_filename.find_last_of('.'));
std::string file_without_extension = base_filename.substr(0, p);

Возможно, должна быть проверка, чтобы справиться с файлами, состоящими исключительно из расширений (то есть .bashrc ...)

Если вы разделите это на отдельные функции, вы сможете повторно использовать отдельные задачи:

template<class T>
T base_name(T const & path, T const & delims = "/\\")
{
  return path.substr(path.find_last_of(delims) + 1);
}
template<class T>
T remove_extension(T const & filename)
{
  typename T::size_type const p(filename.find_last_of('.'));
  return p > 0 && p != T::npos ? filename.substr(0, p) : filename;
}

Код настроен на возможность использования его с различными std::basic_string экземплярами (т. Е. std::string & std::wstring ...)

Недостатком шаблона является требование указать параметр шаблона, если функциям передано const char *.

Так что вы можете:

A) Используйте только std::string вместо шаблонов кода

std::string base_name(std::string const & path)
{
  return path.substr(path.find_last_of("/\\") + 1);
}

B) Предоставить функцию обертывания, используя std::string (в качестве промежуточных, которые, вероятно, будут встроены / оптимизированы)

inline std::string string_base_name(std::string const & path)
{
  return base_name(path);
}

C) Укажите параметр шаблона при вызове с помощью const char *.

std::string base = base_name<std::string>("some/path/file.ext");

Результат

std::string filepath = "C:\\MyDirectory\\MyFile.bat";
std::cout << remove_extension(base_name(filepath)) << std::endl;

Печать

MyFile
25 голосов
/ 15 декабря 2011

_splitpath должен делать то, что вам нужно. Конечно, вы можете сделать это вручную, но _splitpath также обрабатывает все особые случаи.

EDIT:

Как упомянул BillHoag, рекомендуется использовать более безопасную версию _splitpath, которая называется _splitpath_s , когда она доступна.

Или, если вы хотите что-то портативное, вы можете просто сделать что-то вроде этого

std::vector<std::string> splitpath(
  const std::string& str
  , const std::set<char> delimiters)
{
  std::vector<std::string> result;

  char const* pch = str.c_str();
  char const* start = pch;
  for(; *pch; ++pch)
  {
    if (delimiters.find(*pch) != delimiters.end())
    {
      if (start != pch)
      {
        std::string str(start, pch);
        result.push_back(str);
      }
      else
      {
        result.push_back("");
      }
      start = pch + 1;
    }
  }
  result.push_back(start);

  return result;
}

...
std::set<char> delims{'\\'};

std::vector<std::string> path = splitpath("C:\\MyDirectory\\MyFile.bat", delims);
cout << path.back() << endl;
12 голосов
/ 22 июля 2015

Если вы можете использовать boost,

#include <boost/filesystem.hpp>
path p("C:\\MyDirectory\\MyFile.bat");
string basename = p.filename().string();
//or 
//string basename = path("C:\\MyDirectory\\MyFile.bat").filename().string();

Это все.

Я рекомендую вам использовать библиотеку boost.Boost дает вам много удобств при работе с C ++.Поддерживает практически все платформы.Если вы используете Ubuntu, вы можете установить библиотеку boost только одной строкой sudo apt-get install libboost-all-dev (ссылка Как установить boost в Ubuntu? )

12 голосов
/ 13 сентября 2012

Вы также можете использовать API оболочки Path PathFindFileName, PathRemoveExtension. Вероятно, хуже, чем _splitpath для этой конкретной проблемы, но эти API очень полезны для всех видов заданий разбора пути, и они учитывают UNC-пути, прямые косые черты и другие странные вещи.

wstring filename = L"C:\\MyDirectory\\MyFile.bat";
wchar_t* filepart = PathFindFileName(filename.c_str());
PathRemoveExtension(filepart); 

http://msdn.microsoft.com/en-us/library/windows/desktop/bb773589(v=vs.85).aspx

Недостатком является то, что вы должны ссылаться на shlwapi.lib, но я не совсем уверен, почему это недостаток.

10 голосов
/ 07 апреля 2017

Функция:

#include <string>

std::string
basename(const std::string &filename)
{
    if (filename.empty()) {
        return {};
    }

    auto len = filename.length();
    auto index = filename.find_last_of("/\\");

    if (index == std::string::npos) {
        return filename;
    }

    if (index + 1 >= len) {

        len--;
        index = filename.substr(0, len).find_last_of("/\\");

        if (len == 0) {
            return filename;
        }

        if (index == 0) {
            return filename.substr(1, len - 1);
        }

        if (index == std::string::npos) {
            return filename.substr(0, len);
        }

        return filename.substr(index + 1, len - index - 1);
    }

    return filename.substr(index + 1, len - index);
}

Тесты:

#define CATCH_CONFIG_MAIN
#include <catch/catch.hpp>

TEST_CASE("basename")
{
    CHECK(basename("") == "");
    CHECK(basename("no_path") == "no_path");
    CHECK(basename("with.ext") == "with.ext");
    CHECK(basename("/no_filename/") == "no_filename");
    CHECK(basename("no_filename/") == "no_filename");
    CHECK(basename("/no/filename/") == "filename");
    CHECK(basename("/absolute/file.ext") == "file.ext");
    CHECK(basename("../relative/file.ext") == "file.ext");
    CHECK(basename("/") == "/");
    CHECK(basename("c:\\windows\\path.ext") == "path.ext");
    CHECK(basename("c:\\windows\\no_filename\\") == "no_filename");
}
7 голосов
/ 30 января 2018

Самый простой способ в cpp17:

используйте #include эксперимент / файловую систему и имя файла () для имени файла с расширением и stem () без расширения.

   #include <iostream>
    #include <experimental/filesystem>
    namespace fs = std::experimental::filesystem;

    int main()
    {
        string filename = "C:\\MyDirectory\\MyFile.bat";

    std::cout << fs::path(filename).filename() << '\n'
        << fs::path(filename).stem() << '\n'
        << fs::path("/foo/bar.txt").filename() << '\n'
        << fs::path("/foo/bar.txt").stem() << '\n'
        << fs::path("/foo/.bar").filename() << '\n'
        << fs::path("/foo/bar/").filename() << '\n'
        << fs::path("/foo/.").filename() << '\n'
        << fs::path("/foo/..").filename() << '\n'
        << fs::path(".").filename() << '\n'
        << fs::path("..").filename() << '\n'
        << fs::path("/").filename() << '\n';
    }

выход:

MyFile.bat
MyFile
"bar.txt"
".bar"
"."
"."
".."
"."
".."
"/"

Ссылка: cppreference

7 голосов
/ 02 апреля 2015

Из документов C ++ - string :: find_last_of

#include <iostream>       // std::cout
#include <string>         // std::string

void SplitFilename (const std::string& str) {
  std::cout << "Splitting: " << str << '\n';
  unsigned found = str.find_last_of("/\\");
  std::cout << " path: " << str.substr(0,found) << '\n';
  std::cout << " file: " << str.substr(found+1) << '\n';
}

int main () {
  std::string str1 ("/usr/bin/man");
  std::string str2 ("c:\\windows\\winhelp.exe");

  SplitFilename (str1);
  SplitFilename (str2);

  return 0;
}

Выходы:

Splitting: /usr/bin/man
 path: /usr/bin
 file: man
Splitting: c:\windows\winhelp.exe
 path: c:\windows
 file: winhelp.exe
5 голосов
/ 01 декабря 2017

Библиотека boost filesystem также доступна как библиотека experimental/filesystem и была объединена с ISO C ++ для C ++ 17.Вы можете использовать его так:

#include <iostream>
#include <experimental/filesystem>

namespace fs = std::experimental::filesystem;

int main () {
    std::cout << fs::path("/foo/bar.txt").filename() << '\n'
}

Вывод:

"bar.txt"

Он также работает для std::string объектов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...