Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make std::filesystem::directory_iterator to list filenames in order

I am trying to get filenames in a directory "in order". I tried with c++ std::filesystem::directory_iterator to do so. As mentioned in https://en.cppreference.com/w/cpp/filesystem/directory_iterator it provides all filenames in the directory, excluding "." and ".." operator but not in order.

Kindly do help me out, Thanks in advance.

Here is the simple code i used

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

int main 
{
  // path to cereal folder 
  std::string path_to_cereal_folder = "/home/ros2/Documents";

  for (auto& entry : std::experimental::filesystem::directory_iterator(path_to_cereal_folder) )
  {
      std::cout << entry.path() << std::endl;     
  }
  return 0;
}
like image 480
Harsha Phaneendra Avatar asked Jun 16 '20 13:06

Harsha Phaneendra


People also ask

How do I get a list of files in a directory in C++?

You can use opendir / readdir / closedir. Sample code which searches a directory for entry ``name'' is: len = strlen(name); dirp = opendir("."); while ((dp = readdir(dirp)) !=

What is boost :: filesystem :: path?

boost::filesystem::path is the central class in Boost. Filesystem for representing and processing paths. Definitions can be found in the namespace boost::filesystem and in the header file boost/filesystem. hpp . Paths can be built by passing a string to the constructor of boost::filesystem::path (see Example 35.1).

What is Boost Filesystem?

The Boost Filesystem Library provides portable facilities to query and manipulate paths, files, and directories. The motivation for the library is the need to be able to perform portable script-like operations from within C++ programs.

What is Directory_iterator?

(since C++17) directory_iterator is a LegacyInputIterator that iterates over the directory_entry elements of a directory (but does not visit the subdirectories). The iteration order is unspecified, except that each directory entry is visited only once. The special pathnames dot and dot-dot are skipped.


1 Answers

As the reference states out, the iterator has no order. If you want to print the files in some order, you have to use other containers.


Printing in alphabetical order

Steps to take:

  1. Iterate over the files and insert the filenames into the set
  2. Iterate over the (sorted) set and print out the filenames. The entries in the set are sorted automatically.

I adapted your code and came to this solution:

#include <iostream>
#include <filesystem>
#include <set>

//--------------------------------------------------------------------------//
using namespace std;
namespace fs = std::filesystem;

//--------------------------------------------------------------------------//
int main() {
  string path_name = "/bin";

  //--- filenames are unique so we can use a set
  set<fs::path> sorted_by_name;

  for (auto &entry : fs::directory_iterator(path_name))
    sorted_by_name.insert(entry.path());

  //--- print the files sorted by filename
  for (auto &filename : sorted_by_name)
    cout << filename.c_str() << endl;
}

Printing sorted by timestamp

Steps to take:

  1. Iterate over the files and extract the timestamp
  2. Insert the files in a map, with its timestamp as sorting key
  3. Iterate over the (sorted) map and print out the filenames and the timestamp converted into something useful.

The helper function to convert the timestamp into a readable time was taken from here.

#include <iostream>
#include <filesystem>
#include <chrono>
#include <map>

//--------------------------------------------------------------------------//
using namespace std;

//--------------------------------------------------------------------------//
//--- helper function convert timepoint to usable timestamp
template <typename TP>
time_t to_time_t(TP tp) {
  using namespace chrono;
  auto sctp = time_point_cast<system_clock::duration>(tp - TP::clock::now() + system_clock::now());
  return system_clock::to_time_t(sctp);
}

//--------------------------------------------------------------------------//
namespace fs = std::filesystem;
int main()
{
  string path_name = "/bin";
  map<time_t, fs::directory_entry> sort_by_time;

  //--- sort the files in the map by time
  for (auto &entry : fs::directory_iterator(path_name))
    if (entry.is_regular_file()) {
      auto time = to_time_t(entry.last_write_time());
      sort_by_time[time] = entry;
    }

  //--- print the files sorted by time
  for (auto const &[time, entry] : sort_by_time) {
    string timestamp = asctime(std::localtime(&time));
    timestamp.pop_back(); // remove automatic linebreak
    cout << timestamp << "\t " << entry.path().c_str() << endl;
  }
}

Printing sorted by filesize

Steps to take:

  1. Iterate over the files and extract the filesize
  2. Insert the files in a map, with its filesize as sorting key
  3. Iterate over the (sorted) map and print out the filenames and the filesize converted into something useful.

The helper function to convert the filesize into a readable information was taken from the cpp-reference.

#include <iostream>
#include <filesystem>
#include <map>
#include <cmath>

//--------------------------------------------------------------------------//
using namespace std;
namespace fs = std::filesystem;

//--------------------------------------------------------------------------//
//--- helper function convert the filesize into something meaningful
struct HumanReadable { uintmax_t size {}; }; 
template <typename Os> Os& operator<< (Os& os, HumanReadable hr) {
    int i{};
    double mantissa = hr.size;
    for (; mantissa >= 1024.; ++i) {
        mantissa /= 1024.;
    }
    mantissa = std::ceil(mantissa * 10.) / 10.;
    os << mantissa << "BKMGTPE"[i];
    return i == 0 ? os : os << "B (" << hr.size << ')';
}

//--------------------------------------------------------------------------//
int main() {
  string path_name = "/bin";
  map<uintmax_t, fs::directory_entry> sort_by_size;

  //--- sort the files in the map by size
  for (auto &entry : fs::directory_iterator(path_name))
    if (entry.is_regular_file()) {
      auto size = entry.file_size();
      sort_by_size[size] = entry;
    }

  //--- print the files sorted by size
  for (auto const &[size, entry] : sort_by_size)
    cout << HumanReadable{size} << "\t " << entry.path().c_str() << endl;  
}
like image 160
Thomas Wilde Avatar answered Oct 11 '22 05:10

Thomas Wilde