web-dev-qa-db-fra.com

Obtenir un nom de fichier depuis un chemin

Quel est le moyen le plus simple d’obtenir le nom de fichier à partir d’un chemin? 

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

Dans cet exemple, je devrais obtenir "MyFile". sans extension.

66
nidhal

_splitpath devrait faire ce dont vous avez besoin. Vous pouvez bien sûr le faire manuellement, mais _splitpath gère également tous les cas particuliers.

MODIFIER:

Comme BillHoag l'a mentionné, il est recommandé d'utiliser la version plus sûre de _splitpath appelée _splitpath_s lorsqu'elle est disponible.

Ou si vous voulez quelque chose de portable, vous pouvez simplement faire quelque chose comme ça

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

Une solution possible:

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

La solution la plus simple consiste à utiliser quelque chose comme boost::filesystem. Si Pour une raison quelconque, ce n'est pas une option ...

Cela correctement nécessitera un code dépendant du système: sousWindows, '\\' ou '/' peut être un séparateur de chemin; sous Unix, seul '/' fonctionne, et sous d’autres systèmes, qui sait. La solution évidente Serait quelque chose comme:

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

, MatchPathSeparator étant défini dans un en-tête dépendant du système, soit:

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

pour Unix, ou:

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

pour Windows (ou quelque chose de différent pour un autre système inconnu).

EDIT: Il m'a manqué le fait qu'il voulait aussi supprimer l'extension . Pour cela, c'est toujours pareil:

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

Le code est un peu plus complexe, car dans ce cas, la base de L’itérateur inversé est du mauvais côté de l’endroit où nous voulons couper . (Rappelez-vous que la base d’un itérateur inversé est derrière le caractère l'indicateur pointe vers.) Et même ceci est un peu douteux: je n'aime pas le fait qu'il puisse retourner une chaîne vide, par exemple . (Si le seul '.' est le Je dirais que vous devriez renvoyer le nom de fichier complet, ce qui nécessiterait un peu peu de code supplémentaire pour saisir le cas spécial.) }

38
James Kanze

La tâche est assez simple car le nom du fichier de base est simplement la partie de la chaîne commençant par le dernier délimiteur pour les dossiers:

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

Si l'extension doit également être supprimée, la seule chose à faire est de trouver le dernier . et d'emporter une substr à ce stade.

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

Peut-être faudrait-il vérifier si les fichiers ne contiennent que des extensions (c.-à-d. .bashrc...)

Si vous divisez cela en plusieurs fonctions, vous êtes en mesure de réutiliser les tâches simples:

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

Le code est modélisé pour pouvoir être utilisé avec différentes instances de std::basic_string (c'est-à-dire std::string & std::wstring...)

L'inconvénient de la modélisation est la nécessité de spécifier le paramètre de modèle si un const char * est transmis aux fonctions.

Donc vous pouvez soit:

A) Utilisez uniquement std::string au lieu de modéliser le code

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

B) Fournir une fonction de wrapping utilisant std::string (en tant qu'intermédiaires qui seront probablement en ligne/optimisés)

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

C) Spécifiez le paramètre de modèle lorsque vous appelez avec const char *.

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

Résultat

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

Impressions

MyFile
34
Pixelchemist

Vous pouvez également utiliser les API de chemin d'accès au shell PathFindFileName, PathRemoveExtension. Probablement pire que _splitpath pour ce problème particulier, mais ces API sont très utiles pour toutes sortes de travaux d'analyse de chemin d'accès et prennent en compte les chemins UNC, les barres obliques et autres éléments étranges. 

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

L'inconvénient est que vous devez créer un lien vers shlwapi.lib, mais je ne sais pas vraiment pourquoi c'est un inconvénient.

12
Skrymsli

Si vous pouvez utiliser 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();

C'est tout.

Je vous recommande d'utiliser boost library. Boost vous offre beaucoup de commodités lorsque vous travaillez avec C++. Il supporte presque toutes les plateformes . Si vous utilisez Ubuntu, vous pouvez installer la bibliothèque boost avec une seule ligne Sudo apt-get install libboost-all-dev (réf. Comment installer boost sur Ubuntu? )

12
plhn

Une fonction:

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

Tests:

#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

La manière la plus simple dans cpp17 est:

utilisez le #include experimental/filesystem et le nom de fichier () pour le nom de fichier avec extension et stem () sans extension.

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

sortie:

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

Réf: cppreference

7
eliasetm

À partir de documents C++ - 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;
}

Les sorties:

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 (inspirée de la version de James Kanze) avec initialisation uniforme et lambda en ligne anonyme.

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

Cela ne supprime toutefois pas l'extension du fichier.

5
alveko

La bibliothèque boostfilesystem est également disponible en tant que bibliothèque experimental/filesystem et a été fusionnée dans ISO C++ pour C++ 17. Vous pouvez l'utiliser comme ceci:

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

namespace fs = std::experimental::filesystem;

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

Sortie:

"bar.txt"

Cela fonctionne aussi pour les objets std::string.

4
Adam Erickson

c'est la seule chose qui a finalement fonctionné pour moi:

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

à peu près ce que Skrymsli a suggéré mais ne fonctionne pas avec wchar_t *, VS Enterprise 2015 

_splitpath a également fonctionné, mais je n'aime pas devoir deviner combien de caractères [?] il me faut. Certaines personnes ont probablement besoin de ce contrôle, je suppose.

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

Je ne crois pas que des inclus étaient nécessaires pour _splitpath. Aucune bibliothèque externe (comme boost) n’était nécessaire pour l’une ou l’autre de ces solutions.

4
Fractal

Je le ferais par ...

Effectuez une recherche en arrière à partir de la fin de la chaîne jusqu'à la première barre oblique inversée/inversée.

Recherchez ensuite à nouveau à partir de la fin de la chaîne jusqu'à ce que vous trouviez le premier point (.)

Vous avez alors le début et la fin du nom du fichier. 

Simples ...

3
TomP89

Cela devrait fonctionner aussi:

// 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 vous pouvez l'utiliser, Qt fournira QString (avec split, trim etc.), QFile, QPath, QFileInfo etc. pour manipuler des fichiers, des noms de fichiers et des répertoires. Et bien sûr, c'est aussi un dépassement de temps.

2
typedef

Ne pas utiliser _splitpath() et _wsplitpath(). Ils ne sont pas en sécurité et ils sont obsolètes!

Utilisez plutôt leurs versions sûres, à savoir _splitpath_s() et _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;
}

La variable m_szFileName contient le nom de fichier.

2
Lucian

Pendant longtemps, je recherchais une fonction capable de décomposer correctement le chemin du fichier. Pour moi, ce code fonctionne parfaitement pour Linux et 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);
}

Voici des exemples de résultats:

[]
  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:  ''

J'espère que cela vous aide aussi :)

0
no one special

shlwapi.lib/dll utilise la ruche de registre HKCU en interne. 

Il est préférable de ne pas créer de lien vers shlwapi.lib si vous créez une bibliothèque ou si le produit ne dispose pas d'une interface utilisateur. Si vous écrivez une bibliothèque, votre code peut être utilisé dans n'importe quel projet, y compris ceux qui ne possèdent pas d'interface utilisateur. 

Si vous écrivez du code qui s'exécute lorsqu'un utilisateur n'est pas connecté (par exemple, service [ou autre] configuré pour démarrer au démarrage ou au démarrage), il n'y a pas de variable HKCU. Enfin, les shlwapi sont des fonctions d'établissement; et par conséquent en haut de la liste pour décourager les versions ultérieures de Windows. 

0
Brook

Vous pouvez utiliser le système de fichiers std :: pour le faire très bien:

#include <filesystem>
namespace fs = std::experimental::filesystem;

fs::path myFilePath("C:\\MyDirectory\\MyFile.bat");
fs::path filename = myFilePath.stem();
0
GuidedHacking
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