Jaki jest najprostszy sposób uzyskania nazwy pliku ze ścieżki?
string filename = "C:\\MyDirectory\\MyFile.bat"
W tym przykładzie powinienem otrzymać „MyFile”. bez przedłużenia.
Jaki jest najprostszy sposób uzyskania nazwy pliku ze ścieżki?
string filename = "C:\\MyDirectory\\MyFile.bat"
W tym przykładzie powinienem otrzymać „MyFile”. bez przedłużenia.
Odpowiedzi:
_splitpath powinien zrobić to, czego potrzebujesz. Możesz oczywiście zrobić to ręcznie, ale _splitpath
obsługuje również wszystkie specjalne przypadki.
EDYTOWAĆ:
Jak wspomniał BillHoag, zaleca się używanie bezpieczniejszej wersji o _splitpath
nazwie _splitpath_s, jeśli jest dostępna.
Lub jeśli chcesz coś przenośnego, możesz po prostu zrobić coś takiego
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
żadnego z załączników na moim komputerze.
<stdlib.h>
. Jeśli chodzi o przenośność, może możesz wymienić kilka przykładów „doskonale dobrych przenośnych rozwiązań”?
<stdlib.h>
. A oczywistym przenośnym rozwiązaniem jest boost::filesystem
.
_splitpath
w stdlib.h
swojej kopii VS? Następnie możesz wykonać instalację naprawczą VS.
Możliwe rozwiązanie:
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);
}
Zadanie jest dość proste, ponieważ podstawowa nazwa pliku to tylko część ciągu zaczynająca się od ostatniego separatora dla folderów:
std::string base_filename = path.substr(path.find_last_of("/\\") + 1)
Jeśli rozszerzenie ma również zostać usunięte, jedyną rzeczą do zrobienia jest znalezienie ostatniego .
i przejście substr
do tego punktu
std::string::size_type const p(base_filename.find_last_of('.'));
std::string file_without_extension = base_filename.substr(0, p);
Być może powinno być sprawdzanie, aby poradzić sobie z plikami składającymi się wyłącznie z rozszerzeń (tj .bashrc
...)
Jeśli podzielisz to na osobne funkcje, będziesz elastyczny, aby ponownie wykorzystać pojedyncze zadania:
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;
}
Kod jest oparty na szablonie, aby móc go używać z różnymi std::basic_string
instancjami (tj. std::string
& std::wstring
...)
Wadą szablonu jest wymóg określenia parametru szablonu, jeśli a const char *
jest przekazywany do funkcji.
Możesz więc:
std::string
zamiast tworzenia szablonu kodustd::string base_name(std::string const & path)
{
return path.substr(path.find_last_of("/\\") + 1);
}
std::string
(jako półproduktów, które prawdopodobnie będą wstawiane / zoptymalizowane)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;
Wydruki
MyFile
base_name
Najprostszym rozwiązaniem jest użycie czegoś w rodzaju boost::filesystem
. Jeśli z jakiegoś powodu to nie jest opcja ...
Robi to poprawnie będzie wymagało trochę kodu zależnego systemu: pod Windows, albo '\\'
czy '/'
może być separator ścieżka; pod Uniksem '/'
działa tylko , a pod innymi systemami, kto wie. Oczywistym rozwiązaniem byłoby coś takiego:
std::string
basename( std::string const& pathname )
{
return std::string(
std::find_if( pathname.rbegin(), pathname.rend(),
MatchPathSeparator() ).base(),
pathname.end() );
}
, ze MatchPathSeparator
zdefiniowaniem w nagłówku zależnym od systemu jako:
struct MatchPathSeparator
{
bool operator()( char ch ) const
{
return ch == '/';
}
};
dla Uniksa lub:
struct MatchPathSeparator
{
bool operator()( char ch ) const
{
return ch == '\\' || ch == '/';
}
};
dla Windows (lub coś jeszcze innego dla innego nieznanego systemu).
EDYCJA: Przegapiłem fakt, że on też chciał stłumić rozszerzenie. W tym celu więcej tego samego:
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 );
}
Kod jest nieco bardziej złożony, ponieważ w tym przypadku podstawa iteratora odwrotnego znajduje się po złej stronie miejsca, w którym chcemy wyciąć. (Pamiętaj, że podstawa iteratora odwrotnego znajduje się za znakiem, na który iterator wskazuje.) I nawet to jest trochę wątpliwe: nie podoba mi się fakt, że może on na przykład zwrócić pusty ciąg. (Jeśli jedyny '.'
jest pierwszym znakiem nazwy pliku, uważałbym, że powinieneś zwrócić pełną nazwę pliku. Wymagałoby to trochę dodatkowego kodu, aby złapać specjalny przypadek.)}
string::find_last_of
zamiast manipulowania odwrotnymi iteratorami?
string
, więc i tak musisz się ich nauczyć. A gdy się ich nauczyłem, nie ma powodu, aby zawracać sobie głowę nauką całego nadętego interfejsu std::string
.
Najprostszy sposób w C ++ 17 to:
użyj #include <filesystem>
i filename()
dla nazwy pliku z rozszerzeniem i stem()
bez rozszerzenia.
#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';
}
wynik:
MyFile.bat
MyFile
"bar.txt"
".bar"
"."
"."
".."
"."
".."
"/"
Odniesienie: cppreference
Możesz również użyć interfejsów API PathFindFileName powłoki, PathRemoveExtension. Prawdopodobnie gorsze niż _splitpath w przypadku tego konkretnego problemu, ale te interfejsy API są bardzo przydatne dla wszystkich rodzajów zadań parsowania ścieżek i uwzględniają ścieżki UNC, ukośniki i inne dziwne rzeczy.
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
Wadą jest to, że musisz linkować do shlwapi.lib, ale nie jestem pewien, dlaczego jest to wada.
Jeśli możesz użyć wzmocnienia,
#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();
To wszystko.
Polecam skorzystać z biblioteki boost. Boost daje wiele udogodnień podczas pracy z C ++. Obsługuje prawie wszystkie platformy. Jeśli używasz Ubuntu, możesz zainstalować bibliotekę boost tylko o jedną linię sudo apt-get install libboost-all-dev
(zob. Jak zainstalować boost na Ubuntu? )
Funkcjonować:
#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);
}
Testy:
#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");
}
Z C ++ Docs - 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;
}
Wyjścia:
Splitting: /usr/bin/man
path: /usr/bin
file: man
Splitting: c:\windows\winhelp.exe
path: c:\windows
file: winhelp.exe
find_last_of
zwraca, string::npos
jeśli nic nie zostało znalezione.
string::npos
nie musi być wykonywane ze względu na sposób, w jaki to i string::substr
są zaimplementowane. a) string::npos
jest przekazywany jako "length" => substr
ma udokumentowane zachowanie czytania wszystkich do końca. b) substr
ma podane „ string::npos + 1
” i nie ma długości: string::npos
jest udokumentowane, że ma wartość -1
, więc oblicza 0
=> początek ciągu i domyślną wartością długości dla substr
jest npos
=> działa również na „tylko nazwa pliku” cplusplus.com/reference / string / ciąg / SUBSTR cplusplus.com/reference/string/string/npos
Wariant C ++ 11 (zainspirowany wersją Jamesa Kanze) z jednolitą inicjalizacją i anonimową wbudowaną lambdą.
std::string basename(const std::string& pathname)
{
return {std::find_if(pathname.rbegin(), pathname.rend(),
[](char c) { return c == '/'; }).base(),
pathname.end()};
}
Nie usuwa jednak rozszerzenia pliku.
return c == '/' || c == '\\';
aby
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
Biblioteka jest również dostępny jako experimental/filesystem
biblioteki i został włączony do ISO C ++ C ++ 17. Możesz go używać w ten sposób:
#include <iostream>
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
int main () {
std::cout << fs::path("/foo/bar.txt").filename() << '\n'
}
Wynik:
"bar.txt"
Działa również w przypadku std::string
obiektów.
to jedyna rzecz, która w końcu zadziałała dla mnie:
#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);
prawie to, co sugerował Skrymsli, ale nie działa z wchar_t *, VS Enterprise 2015
_splitpath też działało, ale nie lubię zgadywać, ilu znaków [?] będę potrzebować; Myślę, że niektórzy ludzie prawdopodobnie potrzebują tej kontroli.
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);
Nie sądzę, by były potrzebne jakieś dołączenia dla _splitpath. Żadne zewnętrzne biblioteki (takie jak boost) nie były potrzebne do żadnego z tych rozwiązań.
Zrobiłbym to przez ...
Szukaj wstecz od końca ciągu, aż znajdziesz pierwszy ukośnik odwrotny / ukośnik w przód.
Następnie wyszukaj ponownie wstecz od końca ciągu, aż znajdziesz pierwszą kropkę (.)
Masz wtedy początek i koniec nazwy pliku.
Proste ...
'\\'
jako separator ścieżek, również używa '/'
, więc musisz dopasować również.) I nie jestem pewien, czego oczekiwałbyś.
my.source.cpp
zostanie skompilowany do my.source.obj
(z rozszerzeniem .cpp
zamienionym na .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;
}
Zmienna m_szFileName zawiera nazwę pliku.
boost::filesystem::path( path ).filename()
.
Nie używaj _splitpath()
i _wsplitpath()
. Nie są bezpieczne i są przestarzałe!
Zamiast tego użyj ich bezpiecznych wersji, a mianowicie _splitpath_s()
i_wsplitpath_s()
To też powinno działać:
// 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("."));
}
Jeśli możesz go użyć, Qt zapewnia QString (z podziałem, przycinaniem itp.), QFile, QPath, QFileInfo itp. Do manipulowania plikami, nazwami plików i katalogami. I oczywiście jest to również platforma krzyżowa.
getFilename
lub coś w tym rodzaju).
Możesz użyć std :: filesystem, aby zrobić to całkiem przyjemnie:
#include <filesystem>
namespace fs = std::experimental::filesystem;
fs::path myFilePath("C:\\MyDirectory\\MyFile.bat");
fs::path filename = myFilePath.stem();
Długo szukałem funkcji potrafiącej poprawnie rozłożyć ścieżkę do pliku. Dla mnie ten kod działa doskonale zarówno dla Linuksa, jak i Windowsa.
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);
}
Przykładowe wyniki to:
[]
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: ''
Mam nadzieję, że to Ci pomoże :)
shlwapi.lib/dll
używa HKCU
gałęzi rejestru wewnętrznie.
Najlepiej nie podawać linków, shlwapi.lib
jeśli tworzysz bibliotekę lub produkt nie ma interfejsu użytkownika. Jeśli piszesz bibliotekę, kod może być używany w dowolnym projekcie, w tym w tych, które nie mają interfejsów użytkownika.
Jeśli piszesz kod, który działa, gdy użytkownik nie jest zalogowany (np. Usługa [lub inna] ustawiona na uruchamianie podczas rozruchu lub uruchamiania), nie ma HKCU
. Wreszcie shlwapi to funkcje rozliczeniowe; w rezultacie wysoko na liście przestarzałych w nowszych wersjach systemu Windows.
Zaimplementowałem funkcję, która może zaspokoić Twoje potrzeby. Opiera się na funkcji constexpr string_view find_last_of (od C ++ 17), którą można obliczyć w czasie kompilacji
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__);