Який найпростіший спосіб отримати ім’я файлу із шляху?
string filename = "C:\\MyDirectory\\MyFile.bat"
У цьому прикладі я повинен отримати "MyFile". без розширення.
Який найпростіший спосіб отримати ім’я файлу із шляху?
string filename = "C:\\MyDirectory\\MyFile.bat"
У цьому прикладі я повинен отримати "MyFile". без розширення.
Відповіді:
_splitpath повинен робити те, що вам потрібно. Звичайно, ви можете зробити це вручну, але також _splitpath
обробляєте всі особливі випадки.
РЕДАГУВАТИ:
Як уже згадувалося 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;
_splitpath
моїй машині немає жодного з компонентів.
<stdlib.h>
. Що стосується портативності, можливо, ви можете перелічити кілька прикладів «цілком хороших портативних рішень»?
<stdlib.h>
. І головним мотивним портативним рішенням є boost::filesystem
.
_splitpath
в stdlib.h
вашій копії VS? Тоді ви можете зробити ремонтну установку VS.
Можливе рішення:
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);
}
Завдання досить просте, оскільки базове ім'я файлу - це лише частина рядка, що починається з останнього деліметра для папок:
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 *
передається функціям.
Отже, ви можете:
std::string
замість шаблону кодуstd::string base_name(std::string const & path)
{
return path.substr(path.find_last_of("/\\") + 1);
}
std::string
(як проміжних продуктів, які, швидше за все, будуть вбудовані / оптимізовані)inline std::string string_base_name(std::string const & path)
{
return base_name(path);
}
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
base_name
спочатку.)
Найпростішим рішенням є використання чогось подібного 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 (або щось, що все ще відрізняється для іншої невідомої системи).
EDIT: Я пропустив той факт, що він також хотів придушити продовження. Для цього ще те саме:
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 );
}
Код дещо складніший, оскільки в цьому випадку основа зворотного ітератора знаходиться на неправильній стороні місця, де ми хочемо вирізати. (Пам'ятайте, що основа зворотного ітератора стоїть за символом, на який вказує ітератор.) І навіть це трохи сумнівно: мені не подобається той факт, що він може повертати порожній рядок, наприклад. (Якщо єдиним '.'
є перший символ імені файлу, я стверджую, що вам слід повернути повне ім'я файлу. Для цього знадобиться трохи додаткового коду, щоб зафіксувати особливий випадок.)}
string::find_last_of
замість маніпулювання зворотними ітераторами?
string
, тому ви все одно повинні їх вивчити. І вивчивши їх, немає жодної причини турбуватися про вивчення всього роздутого інтерфейсу std::string
.
Найпростіший спосіб у C ++ 17:
використовуйте #include <filesystem>
і filename()
для імені файлу з розширенням та stem()
без розширення.
#include <iostream>
#include <filesystem>
namespace fs = std::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
Ви також можете використовувати оболонку Path APIs 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, але я не зовсім впевнений, чому це недолік.
Якщо ви можете використовувати 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? )
Функція:
#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");
}
З документів C ++ - рядок :: 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
find_last_of
повертається, string::npos
якщо нічого не знайдено.
string::npos
не потрібно робити через спосіб, як це і string::substr
реалізовано. а) string::npos
передається як "length" => substr
має задокументовану поведінку читання всіх до кінця. б) substr
дається « string::npos + 1
» і не довжина: string::npos
не документована мати значення -1
, так що має значення 0
=> початок рядка і значення по замовчуванням довжини для substr
є в npos
=> працює на "просто ім'я файлу" теж cplusplus.com/reference / string / string / substr cplusplus.com/reference/string/string/npos
Варіант С ++ 11 (натхненний версією Джеймса Канце) з рівномірною ініціалізацією та анонімною вбудованою лямбда-ланкою.
std::string basename(const std::string& pathname)
{
return {std::find_if(pathname.rbegin(), pathname.rend(),
[](char c) { return c == '/'; }).base(),
pathname.end()};
}
Однак це не видаляє розширення файлу.
return c == '/' || c == '\\';
if (pathname.size() == 0) return "."; auto iter = pathname.rbegin(); auto rend = pathname.rend(); while (iter != rend && *iter == '/') ++iter; if (iter == rend) /* pathname has only path separators */ return "/"; pathname = std::string(pathname.begin(), iter.base());
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
об'єктів.
це єдине, що насправді нарешті спрацювало у мене:
#include "Shlwapi.h"
CString some_string = "c:\\path\\hello.txt";
LPCSTR file_path = some_string.GetString();
LPCSTR filepart_c = PathFindFileName(file_path);
LPSTR filepart = LPSTR(filepart_c);
PathRemoveExtension(filepart);
майже те, що запропонував Скримслі, але не працює з wchar_t *, VS Enterprise 2015
_splitpath також працював, але мені не подобається вгадувати, скільки символів [[]] мені знадобиться; певно, деяким людям потрібен цей контроль.
CString c_model_name = "c:\\path\\hello.txt";
char drive[200];
char dir[200];
char name[200];
char ext[200];
_splitpath(c_model_name, drive, dir, name, ext);
Я не вірю, що для _splitpath потрібні були будь-які включення. Жодні зовнішні бібліотеки (наприклад, підсилення) не потрібні для жодного з цих рішень.
Я б зробив це ...
Шукайте назад від кінця рядка, поки не знайдете першу косу риску / косу риску.
Потім виконайте пошук назад з кінця рядка, поки не знайдете першу крапку (.)
Потім у вас є початок і кінець імені файлу.
Прості ...
'\\'
як роздільник шляхів, також використовує '/'
, тому вам потрібно збігатися з ними.) І я не впевнений, чого б ви з нетерпінням чекали.
my.source.cpp
компілюється my.source.obj
, наприклад (із .cpp
заміненим розширенням на .obj
).
m_szFilePath.MakeLower();
CFileFind finder;
DWORD buffSize = MAX_PATH;
char longPath[MAX_PATH];
DWORD result = GetLongPathName(m_szFilePath, longPath, MAX_PATH );
if( result == 0)
{
m_bExists = FALSE;
return;
}
m_szFilePath = CString(longPath);
m_szFilePath.Replace("/","\\");
m_szFilePath.Trim();
//check if it does not ends in \ => remove it
int length = m_szFilePath.GetLength();
if( length > 0 && m_szFilePath[length - 1] == '\\' )
{
m_szFilePath.Truncate( length - 1 );
}
BOOL bWorking = finder.FindFile(this->m_szFilePath);
if(bWorking){
bWorking = finder.FindNextFile();
finder.GetCreationTime(this->m_CreationTime);
m_szFilePath = finder.GetFilePath();
m_szFileName = finder.GetFileName();
this->m_szFileExtension = this->GetExtension( m_szFileName );
m_szFileTitle = finder.GetFileTitle();
m_szFileURL = finder.GetFileURL();
finder.GetLastAccessTime(this->m_LastAccesTime);
finder.GetLastWriteTime(this->m_LastWriteTime);
m_ulFileSize = static_cast<unsigned long>(finder.GetLength());
m_szRootDirectory = finder.GetRoot();
m_bIsArchive = finder.IsArchived();
m_bIsCompressed = finder.IsCompressed();
m_bIsDirectory = finder.IsDirectory();
m_bIsHidden = finder.IsHidden();
m_bIsNormal = finder.IsNormal();
m_bIsReadOnly = finder.IsReadOnly();
m_bIsSystem = finder.IsSystem();
m_bIsTemporary = finder.IsTemporary();
m_bExists = TRUE;
finder.Close();
}else{
m_bExists = FALSE;
}
Змінна m_szFileName містить fileName.
boost::filesystem::path( path ).filename()
.
Не використовуйте _splitpath()
та _wsplitpath()
. Вони не в безпеці, і вони застаріли!
Натомість використовуйте їх безпечні версії, а саме _splitpath_s()
та_wsplitpath_s()
Це теж повинно працювати:
// strPath = "C:\\Dir\\File.bat" for example
std::string getFileName(const std::string& strPath)
{
size_t iLastSeparator = 0;
return strPath.substr((iLastSeparator = strPath.find_last_of("\\")) != std::string::npos ? iLastSeparator + 1 : 0, strPath.size() - strPath.find_last_of("."));
}
Якщо ви можете використовувати його, Qt надає QString (з розділенням, обрізанням тощо), QFile, QPath, QFileInfo тощо для маніпулювання файлами, іменами файлів та каталогів. І, звичайно, це також поперечний плафторм.
getFilename
чи щось подібне).
Ви можете використовувати файлову систему std ::, щоб зробити це досить добре:
#include <filesystem>
namespace fs = std::experimental::filesystem;
fs::path myFilePath("C:\\MyDirectory\\MyFile.bat");
fs::path filename = myFilePath.stem();
Довгий час я шукав функцію, здатну правильно розкласти шлях до файлу. Для мене цей код чудово працює як для Linux, так і для Windows.
void decomposePath(const char *filePath, char *fileDir, char *fileName, char *fileExt)
{
#if defined _WIN32
const char *lastSeparator = strrchr(filePath, '\\');
#else
const char *lastSeparator = strrchr(filePath, '/');
#endif
const char *lastDot = strrchr(filePath, '.');
const char *endOfPath = filePath + strlen(filePath);
const char *startOfName = lastSeparator ? lastSeparator + 1 : filePath;
const char *startOfExt = lastDot > startOfName ? lastDot : endOfPath;
if(fileDir)
_snprintf(fileDir, MAX_PATH, "%.*s", startOfName - filePath, filePath);
if(fileName)
_snprintf(fileName, MAX_PATH, "%.*s", startOfExt - startOfName, startOfName);
if(fileExt)
_snprintf(fileExt, MAX_PATH, "%s", startOfExt);
}
Приклади результатів:
[]
fileDir: ''
fileName: ''
fileExt: ''
[.htaccess]
fileDir: ''
fileName: '.htaccess'
fileExt: ''
[a.exe]
fileDir: ''
fileName: 'a'
fileExt: '.exe'
[a\b.c]
fileDir: 'a\'
fileName: 'b'
fileExt: '.c'
[git-archive]
fileDir: ''
fileName: 'git-archive'
fileExt: ''
[git-archive.exe]
fileDir: ''
fileName: 'git-archive'
fileExt: '.exe'
[D:\Git\mingw64\libexec\git-core\.htaccess]
fileDir: 'D:\Git\mingw64\libexec\git-core\'
fileName: '.htaccess'
fileExt: ''
[D:\Git\mingw64\libexec\git-core\a.exe]
fileDir: 'D:\Git\mingw64\libexec\git-core\'
fileName: 'a'
fileExt: '.exe'
[D:\Git\mingw64\libexec\git-core\git-archive.exe]
fileDir: 'D:\Git\mingw64\libexec\git-core\'
fileName: 'git-archive'
fileExt: '.exe'
[D:\Git\mingw64\libexec\git.core\git-archive.exe]
fileDir: 'D:\Git\mingw64\libexec\git.core\'
fileName: 'git-archive'
fileExt: '.exe'
[D:\Git\mingw64\libexec\git-core\git-archiveexe]
fileDir: 'D:\Git\mingw64\libexec\git-core\'
fileName: 'git-archiveexe'
fileExt: ''
[D:\Git\mingw64\libexec\git.core\git-archiveexe]
fileDir: 'D:\Git\mingw64\libexec\git.core\'
fileName: 'git-archiveexe'
fileExt: ''
Сподіваюся, це вам також допоможе :)
shlwapi.lib/dll
використовує HKCU
вулик реєстру внутрішньо.
Краще не посилатись, shlwapi.lib
якщо ви створюєте бібліотеку або продукт не має інтерфейсу користувача. Якщо ви пишете lib, ваш код можна використовувати в будь-якому проекті, включаючи ті, які не мають інтерфейсів.
Якщо ви пишете код, який запускається, коли користувач не входить в систему (наприклад, служба [чи інша], встановлена для запуску під час завантаження або запуску), тоді немає HKCU
. Нарешті, шлвапі - це функції поселення; і, як наслідок, високо в списку для припинення в пізніших версіях Windows.
Я реалізував функцію, яка може відповідати вашим потребам. Він базується на функції cons_prpr string_view find_last_of (починаючи з c ++ 17), яку можна обчислити під час компіляції
constexpr const char* base_filename(const char* p) {
const size_t i = std::string_view(p).find_last_of('/');
return std::string_view::npos == i ? p : p + i + 1 ;
}
//in the file you used this function
base_filename(__FILE__);