¿Cuál es la forma más sencilla de obtener el nombre de archivo de una ruta?
string filename = "C:\\MyDirectory\\MyFile.bat"
En este ejemplo, debería obtener "MyFile". sin extensión.
¿Cuál es la forma más sencilla de obtener el nombre de archivo de una ruta?
string filename = "C:\\MyDirectory\\MyFile.bat"
En este ejemplo, debería obtener "MyFile". sin extensión.
Respuestas:
_splitpath debería hacer lo que necesita. Por supuesto, puede hacerlo manualmente, pero también _splitpath
maneja todos los casos especiales.
EDITAR:
Como mencionó BillHoag, se recomienda usar la versión más segura de _splitpath
llamada _splitpath_s cuando esté disponible.
O si quieres algo portátil, puedes hacer algo como esto
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
ninguna de las inclusiones en mi máquina.
<stdlib.h>
. En cuanto a la portabilidad, ¿quizás pueda enumerar algunos ejemplos de "soluciones portátiles perfectamente buenas"?
<stdlib.h>
. Y la solución portátil más obvia es boost::filesystem
.
_splitpath
en stdlib.h
tu copia de VS? Entonces es posible que desee realizar una instalación de reparación de VS.
Una posible solución:
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);
}
La tarea es bastante simple ya que el nombre del archivo base es solo la parte de la cadena que comienza en el último delimitador de las carpetas:
std::string base_filename = path.substr(path.find_last_of("/\\") + 1)
Si la extensión también debe eliminarse, lo único que debe hacer es encontrar la última .
y llevar substr
a este punto
std::string::size_type const p(base_filename.find_last_of('.'));
std::string file_without_extension = base_filename.substr(0, p);
Quizás debería haber una verificación para hacer frente a los archivos que constan únicamente de extensiones (es decir .bashrc
...)
Si divide esto en funciones separadas, puede reutilizar las tareas individuales:
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;
}
El código está diseñado para poder usarlo con diferentes std::basic_string
instancias (es decir, std::string
& std::wstring
...)
La desventaja de la plantilla es el requisito de especificar el parámetro de plantilla si const char *
se pasa a las funciones.
Entonces podrías:
std::string
lugar de crear una plantilla del códigostd::string base_name(std::string const & path)
{
return path.substr(path.find_last_of("/\\") + 1);
}
std::string
(como intermedios que probablemente estarán en línea / optimizados)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;
Huellas dactilares
MyFile
base_name
La solución más simple es usar algo como boost::filesystem
. Si por alguna razón esta no es una opción ...
Hacer esto correctamente requerirá algún código dependiente del sistema: en Windows, '\\'
o '/'
puede ser un separador de ruta; bajo Unix, solo '/'
funciona, y bajo otros sistemas, quién sabe. La solución obvia sería algo como:
std::string
basename( std::string const& pathname )
{
return std::string(
std::find_if( pathname.rbegin(), pathname.rend(),
MatchPathSeparator() ).base(),
pathname.end() );
}
, MatchPathSeparator
definiéndose en un encabezado dependiente del sistema como:
struct MatchPathSeparator
{
bool operator()( char ch ) const
{
return ch == '/';
}
};
para Unix, o:
struct MatchPathSeparator
{
bool operator()( char ch ) const
{
return ch == '\\' || ch == '/';
}
};
para Windows (o algo aún diferente para algún otro sistema desconocido).
EDITAR: Me perdí el hecho de que él también quería suprimir la extensión. Por eso, más de lo mismo:
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 );
}
El código es un poco más complejo, porque en este caso, la base del iterador inverso está en el lado equivocado de donde queremos cortar. (Recuerde que la base de un iterador inverso está detrás del carácter al que apunta el iterador). E incluso esto es un poco dudoso: no me gusta el hecho de que pueda devolver una cadena vacía, por ejemplo. (Si el único '.'
es el primer carácter del nombre de archivo, yo diría que debería devolver el nombre de archivo completo. Esto requeriría un poco de código adicional para detectar el caso especial.)}
string::find_last_of
lugar de manipular iteradores inversos?
string
, por lo que debe aprenderlos de todos modos. Y habiéndolos aprendido, no hay razón para molestarse en aprender toda la interfaz hinchada std::string
.
También puede utilizar las API de ruta de shell PathFindFileName, PathRemoveExtension. Probablemente sea peor que _splitpath para este problema en particular, pero esas API son muy útiles para todo tipo de trabajos de análisis de rutas y tienen en cuenta las rutas UNC, barras diagonales y otras cosas raras.
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
El inconveniente es que tienes que vincular a shlwapi.lib, pero no estoy muy seguro de por qué eso es un inconveniente.
Si puedes usar 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();
Esto es todo.
Te recomiendo que uses la biblioteca boost. Boost le brinda muchas comodidades cuando trabaja con C ++. Es compatible con casi todas las plataformas. Si usa Ubuntu, puede instalar la biblioteca boost en una sola línea sudo apt-get install libboost-all-dev
(ref. ¿Cómo instalar boost en Ubuntu? )
La forma más sencilla en C ++ 17 es:
utilice #include <filesystem>
y filename()
para el nombre de archivo con extensión y stem()
sin extensión.
#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';
}
salida:
MyFile.bat
MyFile
"bar.txt"
".bar"
"."
"."
".."
"."
".."
"/"
Referencia: cppreference
Función:
#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);
}
Pruebas:
#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");
}
Desde 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;
}
Salidas:
Splitting: /usr/bin/man
path: /usr/bin
file: man
Splitting: c:\windows\winhelp.exe
path: c:\windows
file: winhelp.exe
find_last_of
regresa string::npos
si no se encontró nada.
string::npos
no es necesario realizar la comprobación debido a la forma en que string::substr
se implementan. a) string::npos
se pasa como "longitud" => substr
tiene un comportamiento documentado de leer todo hasta el final. b) substr
se le da " string::npos + 1
" y no tiene longitud: string::npos
se documenta que tiene un valor de -1
, por lo que se evalúa como 0
=> inicio de la cadena y el valor predeterminado de las longitudes substr
es npos
=> también funciona en "solo nombre de archivo" cplusplus.com/reference / string / string / substr cplusplus.com/reference/string/string/npos
Variante de C ++ 11 (inspirada en la versión de James Kanze) con inicialización uniforme y lambda en línea anónimo.
std::string basename(const std::string& pathname)
{
return {std::find_if(pathname.rbegin(), pathname.rend(),
[](char c) { return c == '/'; }).base(),
pathname.end()};
}
Sin embargo, no elimina la extensión del archivo.
return c == '/' || c == '\\';
que funcione en Windows
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());
La boost
filesystem
biblioteca también está disponible como experimental/filesystem
biblioteca y se fusionó con ISO C ++ para C ++ 17. Puedes usarlo así:
#include <iostream>
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
int main () {
std::cout << fs::path("/foo/bar.txt").filename() << '\n'
}
Salida:
"bar.txt"
También funciona para std::string
objetos.
esto es lo único que finalmente funcionó para mí:
#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);
más o menos lo que sugirió Skrymsli pero no funciona con wchar_t *, VS Enterprise 2015
_splitpath también funcionó, pero no me gusta tener que adivinar cuántos caracteres char [?] voy a necesitar; algunas personas probablemente necesitan este control, supongo.
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);
No creo que se necesitara ninguna inclusión para _splitpath. No se necesitaron bibliotecas externas (como boost) para ninguna de estas soluciones.
Lo haría por ...
Busque hacia atrás desde el final de la cadena hasta que encuentre la primera barra invertida / barra inclinada hacia adelante.
Luego busque hacia atrás nuevamente desde el final de la cadena hasta que encuentre el primer punto (.)
Luego tiene el inicio y el final del nombre del archivo.
Simples ...
'\\'
como separador de ruta también lo usa '/'
, por lo que debe hacer coincidir cualquiera de los dos). Y no estoy seguro de lo que estaría esperando.
my.source.cpp
se compila en my.source.obj
, por ejemplo (con la extensión .cpp
reemplazada por .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;
}
La variable m_szFileName contiene el nombre de archivo.
boost::filesystem::path( path ).filename()
.
No use _splitpath()
y _wsplitpath()
. ¡No son seguros y están obsoletos!
En su lugar, utilice sus versiones seguras, a saber _splitpath_s()
y_wsplitpath_s()
Esto también debería funcionar:
// 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("."));
}
Si puede usarlo, Qt proporciona QString (con split, trim, etc.), QFile, QPath, QFileInfo, etc. para manipular archivos, nombres de archivos y directorios. Y, por supuesto, también es una plataforma cruzada.
getFilename
o algo así).
Puede usar el sistema de archivos std :: para hacerlo bastante bien:
#include <filesystem>
namespace fs = std::experimental::filesystem;
fs::path myFilePath("C:\\MyDirectory\\MyFile.bat");
fs::path filename = myFilePath.stem();
Durante mucho tiempo estuve buscando una función capaz de descomponer correctamente la ruta del archivo. Para mí, este código funciona perfectamente tanto para Linux como para 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);
}
Los resultados de ejemplo son:
[]
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: ''
Espero que esto también te ayude :)
shlwapi.lib/dll
utiliza el HKCU
subárbol del registro internamente.
Es mejor no vincularlo shlwapi.lib
si está creando una biblioteca o si el producto no tiene una interfaz de usuario. Si está escribiendo una biblioteca, su código se puede usar en cualquier proyecto, incluidos aquellos que no tienen IU.
Si está escribiendo código que se ejecuta cuando un usuario no está conectado (por ejemplo, servicio [u otro] configurado para iniciarse en el inicio o inicio), entonces no hay HKCU
. Por último, shlwapi son funciones de liquidación; y como resultado alto en la lista para desaprobar en versiones posteriores de Windows.