The power of recursive SAS® macros - How can a simple macro do so much?

From sasCommunity
Jump to: navigation, search
John H. Adams

Boehringer Ingelheim

Ridgefield, CT

Abstract

A recursive macro is a perfect solution if you need to apply a 'fixed process' in a nested application. Using regular macros, you would need to know all the dimensions or levels of nesting in order to set up the appropriate 'nested do loops', resulting in a long complex macro program. With recursive macros, however, you do not need to know the levels of nesting up front, as the macro can call itself over and over again , as needed, to apply a given process.

This paper describes a simple recursive macro, running on a Windows platform, that explores all the branches of a Unix file/directory tree, starting from a given entry point on down. The macro reads each directory, gets a list of dataset names and adds it to the master list. It then opens up each subdirectory it found and reads it. Ultimately, it will have traveled all branches to their ends in order to assemble a full list of datasets (or any other selected file type).

keywords: recursion

Online resources

View the .pdf of this paper.

Macro code

*=========================================*
* Source code for UNIX_LST macro          *
* Program by John H. Adams                *
* 06/19/2002 version 1.1                  *
* (Using a Recursive Macro                *
 =========================================*;
/*===================================================*
 * unix_lst : Reads the directory of a               *
 * unix folder and retrieves the names               *
 * (and attributes) of selected file types,          *
 * ie. file types (extension of files). It will also *
 * continue to look (optional) at all sub-           *
 * directories levels below the initial folder       *
 * and retrieve those file names. The list and       *
 * attributes will be left in the dataset named      *
 * by outdata argument                               *
 * usage : unix_lst(path=,type=, typname=,outdata=,  *
 * subdir= );                                        *
 * assumptions: 1.You are connected to a server      *
 * 2 You have a libref for RWORK                     *
 * (remote WORK folder)                              *
 *---------------------------------------------------*/
 * arguments : path = path to initial folder (req)     *
 *             type = extension(s) of selected         *
 *                    files(opt)                       *
 *                 defaults('ssd01' 'snx01' 'sas7bdat' *
 *                          'sas7bndx')                *
 *             typname= Type Name for selected         *
 *                      files(opt) default (DATASET'   *
 *             outdate = ouput dataset name            *
 *                       containg filelist(req)        *
 *             subdir = Search sub-directories?        *
 *                      [ N or Y] (opt)                *
 *                      default(N)                     *
 *=====================================================*/
 %macro unix_lst(subdir=N,path=, outdata=,
                 type='ssd01' 'snx01' 'sas7bdat' 'sas7bndx',
                 typname=DATASET)
        /des='UNIX folder(s) File Listing Macro';
 
    %local wd1 wd2 wd3 wd4 wd5 i j k dirsw targ tpath
           tpath1 tpath2 tpath3 tdsout ttype ttypname
           lstr targ dirsw selins1 selins2 selins3 selins4
           nds ndc dcn nds1 ndc1 dcn1 nds2 ndc2
           dcn2 nds3 ndc3 dcn3 ;
 
    options nomprint nonotes;
 
 
Rsubmit;
/* Create remote macro %trans to read file of directory */
 
  options nomprint nosource nonotes;
 
 %macro trans(path=,outdata=, typname=DATASET,
              type='ssd01' 'snx01''sas7bdat' 'sas7bndx')
         /des='UNIX Directory read macro';
 
    x cd &path; x ll > $HOME/temp.txt;
    filename temp "$HOME/temp.txt";
 
    data &outdata(drop=file1 file2);
       *** create dirlist dataset ***;
     length file1 file2 file3 file4 file5 type $50
            file $100 path $100;;
     infile temp lrecl=100 firstobs=2 Missover;
 
     input permis $ num1 $ name $ server $ size $ month $
           day $ year $ file1 $ file2 $ file3 $ file4 $ file5 $;
 
     file = trim(file1)||' '||trim(file2)||' '||trim(file3)||' '||trim(file4)
          ||' '||trim(file5);
     type=' '; path = "&path";
 
     if substr(permis,1,1)='d' then type='DIRECTORY';
     else if substr(permis,1,1)='-' and trim(scan(file,2,'.'))
       in(&type) then type="&typname";
 
     if type ^=' ' then output;
  run;
   x rm $HOME/temp.txt;
%mend trans; /* End of remote macro */
 
endrsubmit;
 
 
    /* CREATE THE RECURSIVE PROCESS MACRO */
 
%macro process(lvl=,ttpath=,dnames=, ndnames=,permis=);
 
   %local ndc nds dcn m wd1 wd2 perm ;
 %do m=1 %to &ndnames;
  %let ndc=0; %let nds=0; %let dcn=;
  %let wd1=%scan(&dnames,&m,%str(~));
  %let wd2=%scan(&permis,&m,%str(~));
 
  %if %nrbquote(%substr(&wd2,5,1))= %nrbquote(-) %then
      %put NOTE: No permission to read sub- directory "&wd1";
 
  %else %if
        %index(%upcase(&wd1),%str(MISSING))>0
     or %index(%upcase(&wd1),%str(FILES))>0 %then
      %put NOTE: Sub-directory "&wd1" not explored;
 
  %else %do;
    %let tdsout=tmp;
    %let tpath=&ttpath%str(/&wd1); %put;
    %let lstr=%str(rsubmit;)
              %nrbquote(%)trans(%unquote(&targ))
              %str(; endrsubmit;);
 
    %unquote(&lstr); /* Submit to server */
 
     proc sql noprint;
      %unquote(&selins3;
               &selins1 : ndc &selins4 ;
               &selins2 : dcn &selins4;
               &selins1 : nds &selins5) ;
     quit;
 
    %put Level &lvl._&m PATH=&tpath #D/S=&nds #DIRS=&ndc;
 
    %if &nds >1 %then %do;
     proc append
        base=rwork.&outdate
        data=rwork.&tdsout
        (where=(type="&typname"));
          run;
    %end;
 
    %if &ndc >0 %then
       %process(lvl=&lvl._&m, ttpath=&tpath,dnames=&dcn,
                ndnames=&ndc,permis=&perm);
   %end;
  %end;
 
%mend process; /* End of recursive macro */
 
 
        /* building %trans argument template */
  %let
targ=path=%nrbquote(&)%str(tpath,type=)%nrbquote(&)%str(ttyp
e,outdata=);
 
  %let
targ=&targ%nrbquote(&)%str(tdsout,typname=)%nrbquote(&)ttyp
name;
 
        /* building sql query templates */
  %let selins1=select count(distinct file) into ;
  %let selins2=select distinct file into ;
  %let selins3=select permis into:perm separated by '~' from
    (select distinct file,permis from
     rwork.%nrbquote(&)tdsout where type='DIRECTORY');
  %let selins4=separated by '~' from
          rwork.%nrbquote(&)tdsout where type='DIRECTORY';
  %let selins5=separated by '~' from
          rwork.%nrbquote(&)tdsout where type="&typname";
 
  %let ndc0 =0; %let nds0=0; %let dcn0=;
 
        /*Do Initial folder */
 %let tpath=&path; %let tdsout=&outdata;
 %let ttype=&type; %let ttypname=&typname;
 
 
 
 %let lstr=%str(rsubmit;)%nrbquote(%)trans(%unquote(&targ))
 %str(; endrsubmit;);
 
 %unquote(&lstr);               /* Submit to server */
 
        /* Get Initial folder Info */
    proc sql noprint;
      %unquote(&selins3; &selins1 : ndc0
       &selins4 ; &selins2 : dcn0
       &selins4 ; &selins1 : nds0
       &selins5);
    quit;
 
 %if %upcase(%substr(&subdir.NO,1,1))^=N %then %do;
 
   /* Do levels below Initial folder */
   %let i=1; %let dirsw=1;
   %let tdsout=&outdata;
 
   %put Level 0: STARTING PATH=&tpath #D/S=&nds0
     #DIRS=&ndc0;
 
  %process(lvl=0,ttpath=&tpath,
      dnames=&dcn0,
      ndnames=&ndc0,
      permis=&perm);
   %end;
 
  options nomprint notes;
 
   /* Final re-order of vars, move to WORK */
  proc sql noprint;
   create table &outdata as
   select path,file,type,server, name,size,
   permis,month, day,year
   from rwork.&outdata
   where type="&typname";
  quit;
 
  %mend unix_lst; /*end of main macro*/

See also