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.
c++
visual-c++
nidhal
źródło
źródło
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;
źródło
_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 jestboost::filesystem
._splitpath
wstdlib.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); }
źródło
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ściesubstr
do tego punktustd::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:
A) Używaj tylko
std::string
zamiast tworzenia szablonu kodustd::string base_name(std::string const & path) { return path.substr(path.find_last_of("/\\") + 1); }
B) Zapewnij funkcję owijania przy użyciu
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); }
C) Określ parametr szablonu podczas wywoływania z
const char *
.std::string base = base_name<std::string>("some/path/file.ext");
Wynik
std::string filepath = "C:\\MyDirectory\\MyFile.bat"; std::cout << remove_extension(base_name(filepath)) << std::endl;
Wydruki
źródło
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.)}źródło
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 interfejsustd::string
.Najprostszy sposób w C ++ 17 to:
użyj
#include <filesystem>
ifilename()
dla nazwy pliku z rozszerzeniem istem()
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
źródło
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.
źródło
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? )źródło
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"); }
źródło
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:
źródło
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 istring::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 oblicza0
=> początek ciągu i domyślną wartością długości dlasubstr
jestnpos
=> działa również na „tylko nazwa pliku” cplusplus.com/reference / string / ciąg / SUBSTR cplusplus.com/reference/string/string/nposWariant 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.
źródło
return c == '/' || c == '\\';
abyif (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 jakoexperimental/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.źródło
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ń.
źródło
std::string getfilename(std::string path) { path = path.substr(path.find_last_of("/\\") + 1); size_t dot_i = path.find_last_of('.'); return path.substr(0, dot_i); }
źródło
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 ...
źródło
'\\'
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 domy.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.
źródło
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()
źródło
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.
źródło
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();
źródło
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 :)
źródło
shlwapi.lib/dll
używaHKCU
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.źródło
Powolne, ale proste rozwiązanie regex:
std::string file = std::regex_replace(path, std::regex("(.*\\/)|(\\..*)"), "");
źródło
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__);
źródło