Macros for SAS Application Developers
https://github.com/sasjs/core
mp_dirlist.sas
Go to the documentation of this file.
1 /**
2  @file
3  @brief Returns all files and subdirectories within a specified parent
4  @details When used with getattrs=NO, is not OS specific (uses dopen / dread).
5 
6  Credit for the rename approach:
7  https://communities.sas.com/t5/SAS-Programming/SAS-Function-to-convert-string-to-Legal-SAS-Name/m-p/27375/highlight/true#M5003
8 
9  Usage:
10 
11  %mp_dirlist(path=/some/location, outds=myTable, maxdepth=MAX)
12 
13  %mp_dirlist(outds=cwdfileprops, getattrs=YES)
14 
15  %mp_dirlist(fref=MYFREF)
16 
17  @warning In a Unix environment, the existence of a named pipe will cause this
18  macro to hang. Therefore this tool should be used with caution in a SAS 9 web
19  application, as it can use up all available multibridge sessions if requests
20  are resubmitted.
21  If anyone finds a way to positively identify a named pipe using SAS (without
22  X CMD) do please raise an issue!
23 
24 
25  @param [in] path= (%sysfunc(pathname(work))) Path for which to return contents
26  @param [in] fref= (0) Provide a DISK engine fileref as an alternative to PATH
27  @param [in] maxdepth= (0) Set to a positive integer to indicate the level of
28  subdirectory scan recursion - eg 3, to go `./3/levels/deep`. For unlimited
29  recursion, set to MAX.
30  @param [in] showparent= (NO) By default, the initial parent directory is not
31  part of the results. Set to YES to include it. For this record only,
32  directory=filepath.
33  @param [out] outds= (work.mp_dirlist) The output dataset to create
34  @param [out] getattrs= (NO) If getattrs=YES then the doptname / foptname
35  functions are used to scan all properties - any characters that are not
36  valid in a SAS name (v7) are simply stripped, and the table is transposed
37  so theat each property is a column and there is one file per row. An
38  attempt is made to get all properties whether a file or folder, but some
39  files/folders cannot be accessed, and so not all properties can / will be
40  populated.
41 
42 
43  @returns outds contains the following variables:
44  - directory (containing folder)
45  - file_or_folder (file / folder)
46  - filepath (path/to/file.name)
47  - filename (just the file name)
48  - ext (.extension)
49  - msg (system message if any issues)
50  - level (depth of folder)
51  - OS SPECIFIC variables, if <code>getattrs=</code> is used.
52 
53  <h4> SAS Macros </h4>
54  @li mf_existds.sas
55  @li mf_getvarlist.sas
56  @li mf_wordsinstr1butnotstr2.sas
57  @li mp_dropmembers.sas
58 
59  <h4> Related Macros </h4>
60  @li mp_dirlist.test.sas
61 
62  @version 9.2
63 **/
64 
65 %macro mp_dirlist(path=%sysfunc(pathname(work))
66  , fref=0
67  , outds=work.mp_dirlist
68  , getattrs=NO
69  , showparent=NO
70  , maxdepth=0
71  , level=0 /* The level of recursion to perform. For internal use only. */
72 )/*/STORE SOURCE*/;
73 %let getattrs=%upcase(&getattrs)XX;
74 
75 /* temp table */
76 %local out_ds;
77 data;run;
78 %let out_ds=%str(&syslast);
79 
80 /* drop main (top) table if it exists */
81 %if &level=0 %then %do;
82  %mp_dropmembers(%scan(&outds,-1,.), libref=WORK)
83 %end;
84 
85 data &out_ds(compress=no
86  keep=file_or_folder filepath filename ext msg directory level
87  );
88  length directory filepath $500 fref fref2 $8 file_or_folder $6 filename $80
89  ext $20 msg $200 foption $16;
90  if _n_=1 then call missing(of _all_);
91  retain level &level;
92  %if &fref=0 %then %do;
93  rc = filename(fref, "&path");
94  %end;
95  %else %do;
96  fref="&fref";
97  rc=0;
98  %end;
99  if rc = 0 then do;
100  did = dopen(fref);
101  if did=0 then do;
102  putlog "NOTE: This directory is empty, or does not exist - &path";
103  msg=sysmsg();
104  put (_all_)(=);
105  stop;
106  end;
107  /* attribute is OS-dependent - could be "Directory" or "Directory Name" */
108  numopts=doptnum(did);
109  do i=1 to numopts;
110  foption=doptname(did,i);
111  if foption=:'Directory' then i=numopts;
112  end;
113  directory=dinfo(did,foption);
114  rc = filename(fref);
115  end;
116  else do;
117  msg=sysmsg();
118  put _all_;
119  stop;
120  end;
121  dnum = dnum(did);
122  do i = 1 to dnum;
123  filename = dread(did, i);
124  filepath=cats(directory,'/',filename);
125  rc = filename(fref2,filepath);
126  midd=dopen(fref2);
127  dmsg=sysmsg();
128  if did > 0 then file_or_folder='folder';
129  rc=dclose(midd);
130  midf=fopen(fref2);
131  fmsg=sysmsg();
132  if midf > 0 then file_or_folder='file';
133  rc=fclose(midf);
134 
135  if index(fmsg,'File is in use') or index(dmsg,'is not a directory')
136  then file_or_folder='file';
137  else if index(fmsg,'Insufficient authorization') then file_or_folder='file';
138  else if file_or_folder='' then file_or_folder='locked';
139 
140  if file_or_folder='file' then do;
141  ext = prxchange('s/.*\.{1,1}(.*)/$1/', 1, filename);
142  if filename = ext then ext = ' ';
143  end;
144  else do;
145  ext='';
146  file_or_folder='folder';
147  end;
148  output;
149  end;
150  rc = dclose(did);
151  %if &showparent=YES and &level=0 %then %do;
152  filepath=directory;
153  file_or_folder='folder';
154  ext='';
155  filename=scan(directory,-1,'/\');
156  msg='';
157  level=&level;
158  output;
159  %end;
160  stop;
161 run;
162 
163 %if %substr(&getattrs,1,1)=Y %then %do;
164  data &out_ds;
165  set &out_ds;
166  length infoname infoval $60 fref $8;
167  if _n_=1 then call missing(fref);
168  rc=filename(fref,filepath);
169  drop rc infoname fid i close fref;
170  if file_or_folder='file' then do;
171  fid=fopen(fref);
172  if fid le 0 then do;
173  msg=sysmsg();
174  putlog "Could not open file:" filepath fid= ;
175  sasname='_MCNOTVALID_';
176  output;
177  end;
178  else do i=1 to foptnum(fid);
179  infoname=foptname(fid,i);
180  infoval=finfo(fid,infoname);
181  sasname=compress(infoname, '_', 'adik');
182  if anydigit(sasname)=1 then sasname=substr(sasname,anyalpha(sasname));
183  if upcase(sasname) ne 'FILENAME' then output;
184  end;
185  close=fclose(fid);
186  end;
187  else do;
188  fid=dopen(fref);
189  if fid le 0 then do;
190  msg=sysmsg();
191  putlog "Could not open folder:" filepath fid= ;
192  sasname='_MCNOTVALID_';
193  output;
194  end;
195  else do i=1 to doptnum(fid);
196  infoname=doptname(fid,i);
197  infoval=dinfo(fid,infoname);
198  sasname=compress(infoname, '_', 'adik');
199  if anydigit(sasname)=1 then sasname=substr(sasname,anyalpha(sasname));
200  if upcase(sasname) ne 'FILENAME' then output;
201  end;
202  close=dclose(fid);
203  end;
204  run;
205  proc sort;
206  by filepath sasname;
207  proc transpose data=&out_ds out=&out_ds(drop=_:);
208  id sasname;
209  var infoval;
210  by filepath file_or_folder filename ext ;
211  run;
212 %end;
213 
214 data &out_ds;
215  set &out_ds(where=(filepath ne ''));
216 run;
217 
218 /**
219  * The above transpose can mean that some updates create additional columns.
220  * This necessitates the occasional use of datastep over proc append.
221  */
222 %if %mf_existds(&outds) %then %do;
223  %local basevars appvars newvars;
224  %let basevars=%mf_getvarlist(&outds);
225  %let appvars=%mf_getvarlist(&out_ds);
226  %let newvars=%length(%mf_wordsinstr1butnotstr2(Str1=&appvars,Str2=&basevars));
227  %if &newvars>0 %then %do;
228  data &outds;
229  set &outds &out_ds;
230  run;
231  %end;
232  %else %do;
233  proc append base=&outds data=&out_ds force nowarn;
234  run;
235  %end;
236 %end;
237 %else %do;
238  proc append base=&outds data=&out_ds;
239  run;
240 %end;
241 
242 /* recursive call */
243 %if &maxdepth>&level or &maxdepth=MAX %then %do;
244  data _null_;
245  set &out_ds;
246  where file_or_folder='folder';
247  %if &showparent=YES and &level=0 %then %do;
248  if filepath ne directory;
249  %end;
250  length code $10000;
251  code=cats('%nrstr(%mp_dirlist(path=',filepath,",outds=&outds"
252  ,",getattrs=&getattrs,level=%eval(&level+1),maxdepth=&maxdepth))");
253  put code=;
254  call execute(code);
255  run;
256 %end;
257 
258 /* tidy up */
259 proc sql;
260 drop table &out_ds;
261 
262 %mend mp_dirlist;