BookmarkSubscribeRSS Feed
🔒 This topic is solved and locked. Need further help from the community? Please sign in and ask a new question.
Jsendzik
Fluorite | Level 6

Hello world,

I'm fairly new to SAS and need some help. I'm trying to write a macro in EG that can open up a directory and create a data set that gives the size and file name of all files in that directory. I've seen some great examples in SAS papers but they require the pipe function and I don't have permission to use PIPE.

So far I've had success in doing two things.

1) Get all the filename's written to the log. This is done with a macro I found on SAS support.

%macro drive(dir,ext);

  %let filrf=mydir;

  /* Assigns the fileref of mydir to the directory and opens the directory */

  %let rc=%sysfunc(filename(filrf,&dir));

  %let did=%sysfunc(dopen(&filrf));

  /* Returns the number of members in the directory */

  %let memcnt=%sysfunc(dnum(&did));

  /* Loops through entire directory */

  %do i = 1 %to &memcnt;

  /* Returns the extension from each file */

  %let name=%qscan(%qsysfunc(dread(&did,&i)),-1,.);

  /* Checks to see if file contains an extension */

  %if %qupcase(%qsysfunc(dread(&did,&i))) ne %qupcase(&name) %then

  %do;

  /* Checks to see if the extension matches the parameter value */

  /* If condition is true prints the full name to the log */

  %if (%superq(ext) ne and %qupcase(&name) = %qupcase(&ext)) or

  (%superq(ext) = and %superq(name) ne) %then

  %do;

  %put %qsysfunc(dread(&did,&i));

  %end;

  %end;

  %end;

  /* Closes the directory */

  %let rc=%sysfunc(dclose(&did));

%mend drive;


2) Get the size of one file at a time. I used the previous macro's output to manually make macro variables. Then feed them into this code.

%let Invoice=\\mydir;

%let file1=<the file name returned by the last macro>


data info;

   length infoname infoval $60;

   drop rc fid infonum i close;

   rc=filename("abc","&invoice.&file1.");

   fid=dopen("abc");

   infonum=fnum(fid);

   do i=1 to infonum;

      infoname=foptname(fid,i);

      infoval=finfo(fid,infoname);

      output;

   end;

   close=fclose(fid);

run;

A major problem with this is it only handles one file at a time, and I need all of this information to go into one location so I can look for files with the same size. I know I could make this into a loop that will output a bunch of datasets but this doesn't seem very efficient.

When I envision this is see the first macro storing all file names as individual macro variables. I know this can be done with a loop to update a %let statement <%let file&I. = maybe dread(&did,&I.)>. But the right side of this equality is a major road block for me.

After getting the file names stored, I need to grab the file size. I'm having a hard time doing this because i know i need to use finfo() and I have a dopen() situation. Is there a way to trickle down so to speak? Stating at dopen() and eventually using fopen() so that I can use finfo().

Major Questions:

How can I store system output functions as macro variable?

How can I go from a dopen() situation to a finfo() situation?

As a new programmer I'd really like guidance more than anything. I truly want to learn this not just have the code written for me. Any help would be much appreciated. 

1 ACCEPTED SOLUTION

Accepted Solutions
TomKari
Onyx | Level 15

Here's some code that I've used for a while. You may need to change the date and time informats, as the "dir" command uses your system settings.

Tom

data dirlist;

length test_rec $ 4096;

retain vol_let vol_name vol_ser dir_name first_dir;
length dir_name first_dir $ 1024;
length file_name $ 256;
length file_prefix $ 256;
length file_ext $ 256;
format file_date yymmdd10.;
format file_time time5.;

drop test_rec num_dots num_slash;

infile "C:\listing.txt";
input;

test_rec = _infile_;

if substr(test_rec, 1, 1) ^= " " then
do;
   if substr(test_rec, 25, 5) = "<DIR>" | substr(test_rec, 25, 10) = "<JUNCTION>" then; else
   do;
      file_date = input(substr(test_rec, 1, 10), ddmmyy10.);
      file_time = input(substr(test_rec, 13, 8), time8.);
      file_size = input(substr(test_rec, 22, 17), comma17.);
      file_name = substr(trim(test_rec), 40);
      num_dots = countc(file_name, ".", "o");
      if num_dots = 1 then
      do;
         file_prefix = scan(file_name, 1, ".");
         file_ext = scan(file_name, 2, ".");
      end; else
      if num_dots = 0 then
      do;
         file_prefix = file_name;
         file_ext = "";
      end; else
      do;
         file_prefix = reverse(scan(reverse(file_name), 2, "."));
         file_ext = reverse(scan(reverse(file_name), 1, "."));
      end;
      output;
   end;
end; else

if substr(test_rec, 1, 14) = " Directory of " then
do;
  dir_name = substr(trim(test_rec), 15);
  num_slash = countc(dir_name, "\", "o");
  if num_slash = 1
  then first_dir = dir_name;
  else first_dir = scan(dir_name, 1, "\")||"\"||scan(dir_name, 2, "\");
end; else

if substr(test_rec, 1, 17) = " Volume in drive " then
do;
  vol_let = substr(test_rec, 18, 1);
  vol_name = substr(trim(test_rec), 23);
end; else

if substr(test_rec, 1, 25) = " Volume Serial Number is " then
do;
  vol_ser = substr(trim(test_rec), 26);
end;

run;

View solution in original post

13 REPLIES 13
DBailey
Lapis Lazuli | Level 10

what operating system do you use?  You may be able to do this more efficiently using operating system commands.

DBailey
Lapis Lazuli | Level 10

sorry..just saw the no pipes requirement.  Can you execute batch files?

Jsendzik
Fluorite | Level 6

I'm not sure If I can. I can tell you I'm running SAS EG on a windows XP system.

Jsendzik
Fluorite | Level 6

I'm writing a batch file now to see if I can use them

Jsendzik
Fluorite | Level 6

Okay just wrote one that would ping google. It worked fine. Yes I can execute batch files.

DBailey
Lapis Lazuli | Level 10

So for all files on your c: drive, you could execute a batch program which gets a directory listing:

dir /s c:\ *.* > listing.txt

and then read listing.txt to parse out directory names, file names, and file sizes.

Jsendzik
Fluorite | Level 6

Thanks ill try this now.

TomKari
Onyx | Level 15

Here's some code that I've used for a while. You may need to change the date and time informats, as the "dir" command uses your system settings.

Tom

data dirlist;

length test_rec $ 4096;

retain vol_let vol_name vol_ser dir_name first_dir;
length dir_name first_dir $ 1024;
length file_name $ 256;
length file_prefix $ 256;
length file_ext $ 256;
format file_date yymmdd10.;
format file_time time5.;

drop test_rec num_dots num_slash;

infile "C:\listing.txt";
input;

test_rec = _infile_;

if substr(test_rec, 1, 1) ^= " " then
do;
   if substr(test_rec, 25, 5) = "<DIR>" | substr(test_rec, 25, 10) = "<JUNCTION>" then; else
   do;
      file_date = input(substr(test_rec, 1, 10), ddmmyy10.);
      file_time = input(substr(test_rec, 13, 8), time8.);
      file_size = input(substr(test_rec, 22, 17), comma17.);
      file_name = substr(trim(test_rec), 40);
      num_dots = countc(file_name, ".", "o");
      if num_dots = 1 then
      do;
         file_prefix = scan(file_name, 1, ".");
         file_ext = scan(file_name, 2, ".");
      end; else
      if num_dots = 0 then
      do;
         file_prefix = file_name;
         file_ext = "";
      end; else
      do;
         file_prefix = reverse(scan(reverse(file_name), 2, "."));
         file_ext = reverse(scan(reverse(file_name), 1, "."));
      end;
      output;
   end;
end; else

if substr(test_rec, 1, 14) = " Directory of " then
do;
  dir_name = substr(trim(test_rec), 15);
  num_slash = countc(dir_name, "\", "o");
  if num_slash = 1
  then first_dir = dir_name;
  else first_dir = scan(dir_name, 1, "\")||"\"||scan(dir_name, 2, "\");
end; else

if substr(test_rec, 1, 17) = " Volume in drive " then
do;
  vol_let = substr(test_rec, 18, 1);
  vol_name = substr(trim(test_rec), 23);
end; else

if substr(test_rec, 1, 25) = " Volume Serial Number is " then
do;
  vol_ser = substr(trim(test_rec), 26);
end;

run;

Jsendzik
Fluorite | Level 6

Thanks a bunch. I'll try this out now.

Jsendzik
Fluorite | Level 6

I noticed that you have an in-file statement in the top portion. Is this a text document that lists all files within the directory? Or is that the file input i.e. what we eventually get output for.

TomKari
Onyx | Level 15

In 's post, the "dir /s c:\ *.* > listing.txt" will create a file named listing.txt that contains the results of the "dir" statement, in other words a listing of all of the files on the disk.

In my code, the "infile "C:\listing.txt";" statement will establish this as the file that I read the directory information from, in the "input;" statement. Using an input statement with just a semicolon will read a record, and store it in a variable named _infile_, which I then copy to test_rec and parse.

Jsendzik
Fluorite | Level 6

I did not make that connection at all. That was a complete AHHAA moment. Thanks for your help.

Jsendzik
Fluorite | Level 6

WOW WOW Wow. Thank you all so much it works and I got to start learning about .bat files. I'm now one step closer to becoming an even lazier programmer.

sas-innovate-2024.png

Available on demand!

Missed SAS Innovate Las Vegas? Watch all the action for free! View the keynotes, general sessions and 22 breakouts on demand.

 

Register now!

How to Concatenate Values

Learn how use the CAT functions in SAS to join values from multiple variables into a single value.

Find more tutorials on the SAS Users YouTube channel.

Click image to register for webinarClick image to register for webinar

Classroom Training Available!

Select SAS Training centers are offering in-person courses. View upcoming courses for:

View all other training opportunities.

Discussion stats
  • 13 replies
  • 2999 views
  • 6 likes
  • 3 in conversation