108 votes

Obtenir un nom de fichier à partir d'un chemin

Quelle est la façon la plus simple d'obtenir le nom du fichier à partir d'un chemin ?

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

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

2 votes

Rechercher à partir de l'arrière jusqu'à ce que vous frappiez un retour arrière ?

2 votes

@KerrekSB, vous voulez dire barre oblique inversée ;)

0 votes

J'ai un std::string qui contient le chemin d'un fichier "c : \\MyDirectory\\Myfile.pdf "Je dois renommer ce fichier en monfichier_md.pdf et je dois donc obtenir le nom du fichier à partir du chemin.

86voto

Pixelchemist Points 3636

La tâche est assez simple puisque le nom de fichier de base est juste la partie de la chaîne qui commence au dernier délimiteur pour les dossiers :

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

Si l'extension doit être supprimée également, la seule chose à faire est de trouver la dernière . et prendre un 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 qu'il devrait y avoir une vérification pour faire face aux fichiers constitués uniquement d'extensions (c'est à dire .bashrc ...)

Si vous divisez cette tâche en plusieurs fonctions distinctes, vous pourrez réutiliser les tâches individuelles :

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 l'utiliser avec différents types d'applications. std::basic_string (c'est-à-dire std::string & std::wstring ...)

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

Donc vous pouvez soit :

A) Utiliser 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 d'enveloppement en utilisant std::string (en tant qu'intermédiaires qui seront probablement inlined / optimisé loin)

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

C) Spécifiez le paramètre de modèle lors de l'appel 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;

Imprimés

MyFile

68voto

hmjd Points 76411

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

0 votes

Le plus simple est toujours le meilleur !

54voto

eliasetm Points 843

La méthode la plus simple en C++17 est :

utiliser le #include <filesystem> y filename() pour un nom de fichier avec extension et stem() sans extension.

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

sortie :

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

Référence : Référence cpp

0 votes

Il n'est plus en "expérimental"

39voto

James Kanze Points 96599

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

Pour effectuer cette opération correctement, il faudra utiliser un code dépendant du système : sous Windows, soit '\\' o '/' peut être un séparateur de chemin ; sous Unix, seulement '/' fonctionne, et sous d'autres systèmes, qui sait. La solution évidente solution 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() );
}

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

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). inconnu).

EDIT : J'ai oublié qu'il voulait aussi supprimer l'extension. Pour cela, plus de la même chose :

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 inverse est sur la mauvaise position. l'itérateur inverse est du mauvais côté de l'endroit où nous voulons couper. (Rappelez-vous que la base d'un itérateur inversé est un derrière le caractère vers lequel l'itérateur pointe). Et même cela est un peu douteux : je n'aime pas le fait que t n'aime pas le fait qu'il puisse retourner une chaîne vide, par exemple. (Si le seul '.' est le premier caractère du nom de fichier, je soutiens que que vous devriez retourner le nom de fichier complet. Cela nécessiterait un peu un peu de code supplémentaire pour attraper le cas spécial). }

32voto

Claptrap Points 21299

_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.

EDITAR:

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

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

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;

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X