it-swarm-pt.tech

Obter um nome de arquivo de um caminho

Qual é a maneira mais simples de obter o nome do arquivo de um caminho? 

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

Neste exemplo, eu deveria receber "MyFile". sem extensão.

66
nidhal

_splitpath deve fazer o que você precisa. Você poderia fazer isso manualmente, mas _splitpath também lida com todos os casos especiais.

EDITAR:

Como o BillHoag mencionou, é recomendado usar a versão mais segura do _splitpath chamada _splitpath_s quando disponível.

Ou se você quiser algo portátil você poderia apenas fazer algo parecido com isto

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;
26
Anders

Uma solução possível:

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);
}
41
hmjd

A solução mais simples é usar algo como boost::filesystem. Se por algum motivo isso não é uma opção ...

Fazer isso corretamente exigirá algum código dependente do sistema: em Windows, '\\' ou '/' pode ser um separador de caminho; em Unix, apenas '/' funciona e, sob outros sistemas, quem sabe. A solução óbvia Seria algo como:

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

, com MatchPathSeparator sendo definido em um cabeçalho dependente do sistema como:

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

para Unix ou:

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

para Windows (ou algo ainda diferente para algum outro sistema desconhecido ).

EDIT: Eu perdi o fato de que ele também queria suprimir a extensão. Para isso, mais do mesmo:

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 );
}

O código é um pouco mais complexo, porque neste caso, a base de O iterador reverso está do lado errado de onde queremos cortar. (Lembre-se que a base de um reverso O iterador é um por trás do caractere que o iterador aponta.) E até isso é um pouco duvidoso: Eu não gosto do fato de que ele pode retornar uma string vazia, por exemplo. ____.] (Se o único '.' for o primeiro caractere do nome do arquivo, eu diria Que você deve retornar o nome completo do arquivo. Isso exigiria um pequeno Bit de código extra para capturar o nome do arquivo. caso especial.) }

38
James Kanze

A tarefa é bastante simples, pois o nome do arquivo base é apenas a parte da string que começa no último delimitador para as pastas:

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

Se a extensão for removida, a única coisa a fazer é encontrar o último . e tomar uma substr até este ponto

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

Talvez deva haver uma verificação para lidar com os arquivos que consistem apenas em extensões (por exemplo, .bashrc...)

Se você dividir isso em funções separadas, será flexível para reutilizar as tarefas individuais:

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;
}

O código é modelado para poder usá-lo com instâncias std::basic_string diferentes (por exemplo, std::string & std::wstring...)

A desvantagem da modelagem é o requisito para especificar o parâmetro template se um const char * for passado para as funções.

Então você poderia:

A) Use somente std::string em vez de modelar o código

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

B) Fornecer função de agrupamento usando std::string (como intermediários que provavelmente serão inlined/optimized away)

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

C) Especifique o parâmetro template ao chamar com const char *.

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

Resultado

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

Impressões

MyFile
34
Pixelchemist

Você também pode usar as APIs do caminho do shell PathFindFileName, PathRemoveExtension. Provavelmente pior que _splitpath para esse problema em particular, mas essas APIs são muito úteis para todos os tipos de tarefas de análise de caminho e levam em conta caminhos UNC, barras e outras coisas estranhas. 

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

http://msdn.Microsoft.com/pt-br/library/windows/desktop/bb773589(v=vs.85).aspx

A desvantagem é que você tem que linkar para shlwapi.lib, mas não tenho certeza do porque isso é uma desvantagem.

12
Skrymsli

Se você pode usar impulso, 

#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();

Isso é tudo.

Eu recomendo que você use biblioteca de reforço. O Boost oferece muitas conveniências quando você trabalha com C++. Ele suporta quase todas as plataformas. Se você usa o Ubuntu, você pode instalar a biblioteca de reforço apenas por uma linha Sudo apt-get install libboost-all-dev (ref. Como instalar o boost no Ubuntu? )

12
plhn

Função:

#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);
}

Testes:

#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");
}
10
Rian Quinn

A maneira mais simples no cpp17 é:

use o #include experimental/filesystem e filename () para filename com extension e stem () sem extensão.

   #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';
    }

saída:

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

Ref: cppreference

7
eliasetm

Do 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;
}

Saídas:

Splitting: /usr/bin/man
 path: /usr/bin
 file: man
Splitting: c:\windows\winhelp.exe
 path: c:\windows
 file: winhelp.exe
7
jave.web

Variante C++ 11 (inspirada na versão de James Kanze) com inicialização uniforme e lambda inline anónima.

std::string basename(const std::string& pathname)
{
    return {std::find_if(pathname.rbegin(), pathname.rend(),
                         [](char c) { return c == '/'; }).base(),
            pathname.end()};
}

Não remove a extensão de arquivo embora.

5
alveko

A biblioteca boostfilesystem também está disponível como a biblioteca experimental/filesystem e foi mesclada no ISO C++ for C++ 17. Você pode usá-lo assim:

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

namespace fs = std::experimental::filesystem;

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

Saída:

"bar.txt"

Também funciona para objetos std::string.

4
Adam Erickson

esta é a única coisa que realmente funcionou para mim:

#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);

praticamente o que Skrymsli sugeriu, mas não funciona com wchar_t *, VS Enterprise 2015 

_splitpath também funcionava, mas eu não gosto de ter que adivinhar quantos caracteres char [?] eu vou precisar; algumas pessoas provavelmente precisam desse controle, eu acho.

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);

Eu não acredito que qualquer inclusão seja necessária para o _splitpath. Nenhuma biblioteca externa (como boost) foi necessária para qualquer uma dessas soluções.

4
Fractal

Eu faria isso por ...

Procure para trás a partir do final da string até encontrar a primeira barra invertida/barra invertida.

Em seguida, pesquise de trás para a frente a partir do final da string até encontrar o primeiro ponto (.)

Você então tem o início e o fim do nome do arquivo. 

Simples ...

3
TomP89

Isso deve funcionar também:

// 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("."));
}

Se você puder usá-lo, o Qt fornece QString (com split, trim etc), QFile, QPath, QFileInfo, etc. para manipular arquivos, nomes de arquivos e diretórios. E é claro que também é cross plaftorm.

2
typedef

Não use _splitpath() e _wsplitpath(). Eles não são seguros e são obsoletos!

Em vez disso, use suas versões seguras, ou seja, _splitpath_s() e _wsplitpath_s()

2
hkBattousai
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;
}

A variável m_szFileName contém o nome do arquivo.

2
Lucian

Por muito tempo eu estava procurando por uma função capaz de decompor corretamente o caminho do arquivo. Para mim, esse código está funcionando perfeitamente para Linux e 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);
}

Exemplos de resultados são:

[]
  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 isso ajude você também :)

0
no one special

shlwapi.lib/dll usa o registro HKCU Hive internamente. 

É melhor não vincular a shlwapi.lib se você estiver criando uma biblioteca ou o produto não tiver uma interface do usuário. Se você está escrevendo um lib, então seu código pode ser usado em qualquer projeto, incluindo aqueles que não têm UIs. 

Se você estiver escrevendo um código que é executado quando um usuário não está conectado (por exemplo, serviço [ou outro] definido para iniciar na inicialização ou inicialização), não há HKCU. Por fim, shlwapi são funções de liquidação; e, como resultado, alto na lista para desaprovar em versões posteriores do Windows. 

0
Brook
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);
}
0
X Stylish