Reputation: 205
How can I print a list of all the files in a given directory using Fortran, limiting only to specific file types... For instance I only want to get the list of '.txt' files from a directory.
Files in directory:
something like
WHILE (not_last_file AND filetype = '.txt' )
{
print FILENAME
}
I would really appreciate your help,
Thanks
Upvotes: 6
Views: 1774
Reputation: 458
Although interfacing POSIX commands works, a quick and (somewhat) dirty way to do the same is to just use system's directory listing commands through execute_command_line
, redirecting output to a temporary file. Then you can just read that file to get the listing, and delete the file when it is not needed anymore.
A possible Fortran implementation can be (including some error proofing):
subroutine DirList(directory, flags)
use, intrinsic :: iso_fortran_env, only: error_unit
implicit none
character(len=*), intent(in) :: directory
character(len=*), intent(in), optional :: flags
integer :: tempunit, stat
character(len=255) :: filename
call execute_command_line("ls -1"//flags//" "//directory//" > temp", exitstat=stat)
if (stat/=0) then
write(unit=error_unit, fmt="('Error reading directory ',a)") directory
return
end if
open(newunit=tempunit, file="temp", status="old", action="read")
do
read(unit=tempunit, fmt="(a)", iostat=stat) filename
if (stat < 0) exit
print "(a)",trim(filename)
end do
close(unit=tempunit)
call execute_command_line("rm temp")
end subroutine DirList
Here, ls -1
is used, but dir -1
should work on POSIX as well. The -1
flag is used by default so that output is one string per line (this just saves the extra work to read strings till a blank space). The optional argument flags
can be used to add extra flags, if needed.
Such a subroutine can be used for various tasks, e.g.:
call DirList("your-directory") ! List all files and subdirectories
call DirList("your-directory/*.txt" ! List *.txt files only
call DirList("your-directory/*/", "d") ! List subdirectories only
It may look like a "cheap" approach, but it works and has the extra benefit it is not restricted to POSIX systems only. For example, the subroutine above should work on Windows by replacing ls -1
with dir /b
as the system's command for listing files, and rm
with del
for deleting the temporary file. A more sophisticated version could use the pre-processor to set those system commands as appropriate, depending the operating system the code is compiled.
A function returning an allocatable vector containing the file/subdirectory names can be easily implemented by modifying the subroutine above, if needed.
Upvotes: 0
Reputation: 60000
MoonKnights's answer quotes janneb's answer in the linked question, which was had been closed because the actual question was just one unclear sentence. The quoted "On a POSIX system using recent Fortran compiler, you can use ISO_C_BINDING to create interfaces to the POSIX opendir()
and readdir()
" is completely true, but requires some work in the C language as the dirent
structure is not easily and portably translatable to Fortran and it is better to work with just an opaque pointer there.
I use this:
#include <dirent.h>
#include <stdlib.h>
#include <string.h>
void* open_dir(const char * const dir_name){
DIR* d;
d = opendir(".");
return d;
}
void close_dir(DIR *d){
if (d) closedir(d);
}
void next_file(DIR *d, char ch[256], int* len){
do {
struct dirent *dir = readdir(d);
if (dir){
if (dir->d_type == DT_REG)
{
strncpy(ch, dir->d_name, 256);
size_t slen = strlen(dir->d_name);
if (slen > 255) {
*len = 255;
} else {
*len = (int) slen;
}
} else {
*len = -1;
}
} else {
*len = 0;
}
}
while (*len<0);
}
The code assumes that NAME_MAX==255
. That will be true on most systems and avoids the need to get the value of that macro in Fortran. If that is a problem, you need to get the value of NAME_MAX
to Fortran somehow or declare the header of next file
in some different way.
The Fortran interfaces are then
interface
function open_dir(dir_name) result(res) bind(C, name="open_dir")
use iso_c_binding
type(c_ptr) :: res
character(kind=c_char, len=1), intent(in) :: dir_name(*)
end function
subroutine close_dir(dir) bind(C, name="close_dir")
use iso_c_binding
type(c_ptr), value :: dir
end subroutine
subroutine next_file(dir, file_name, name_len) bind(C, name="next_file")
use iso_c_binding
type(c_ptr), value :: dir
character(kind=c_char, len=1), intent(out) :: file_name(256)
integer(c_int), intent(out) :: name_len
end subroutine
end interface
and you can loop over the files in the directory as
type(c_ptr) :: dir_ptr = c_null_ptr
integer(c_int) :: name_len
character(256) :: file_name
dir_ptr = open_dir("."//c_null_char)
do
call next_file(dir_ptr, file_name, name_len)
print *, file_name(1:name_len)
end do
call close_dir(dir_ptr);
The C code can be easily modified if you also need to list subdirectories (an DT_DIR
branch after the DT_REG
one or just use ||
).
Upvotes: 1
Reputation: 23833
In a few words, you can't. There's no intrinsic library for such operations in Fortran that helps you. How you approach this problem will also depend on the version of Fortran you are running (F77, F90, F95 etc.) which you do not state.
"On a POSIX system using recent Fortran compiler, you can use ISO_C_BINDING to create interfaces to the POSIX opendir() and readdir() functions (or readdir_r() if you need thread safety), which allow you to iterate over the directory entries."
See this post Listing the contents of a directory in Fortran or you could also look at this overview from the gfortran documentation useful.
I hope this helps.
Upvotes: 3