├── LICENSE ├── README.md ├── bench.sas ├── catx.sas ├── cfmtgen.sas ├── compbl.sas ├── contents.sas ├── contentv.sas ├── csv2ds.sas ├── csv_vnext.sas ├── csvfile.sas ├── curdir.sas ├── dbcon.sas ├── dblibchk.sas ├── direxist.sas ├── dirtree.sas ├── dquote.sas ├── ds2post.sas ├── dslist.sas ├── fileref.sas ├── fread.sas ├── github_include.sas ├── lowcase.sas ├── macdelete.sas ├── maclist.sas ├── missing.sas ├── mvartest.sas ├── nobs.sas ├── nvalid.sas ├── parmv.sas ├── parsem.sas ├── qcatx.sas ├── qcompbl.sas ├── qlist.sas ├── qlowcase.sas ├── qsubstrn.sas ├── qsysget.sas ├── replace_crlf.sas ├── safe_ds2csv.sas ├── sas2xport.sas ├── squote.sas ├── subnet.sas ├── substrn.sas ├── symget.sas ├── syslput612.sas ├── tdexist.sas ├── varexist.sas ├── varinfo.sas ├── xport2sas.sas └── xpttype.sas /README.md: -------------------------------------------------------------------------------- 1 | # macros 2 | SAS Utility Macros 3 | 4 | A collection of SAS macros of general utility that enhance or extend the SAS macro language. 5 | 6 | Macros that return no code at all 7 | 8 | [bench](bench.sas) [dblibchk](dblibchk.sas) [fread(mode=2)](fread.sas) [macdelete](macdelete.sas) 9 | [nobs(mvar supplied)](nobs.sas) [parmv](parmv.sas) [syslput612](syslput612.sas) 10 | 11 | Macros that return string 12 | 13 | [catx](catx.sas) [compbl](compbl.sas) [curdir](curdir.sas) 14 | [direxist](direxist.sas) [dquote](dquote.sas) [fileref](fileref.sas) [fread](fread.sas) 15 | [lowcase](lowcase.sas) [missing](missing.sas) [mvartest](mvartest.sas) [nobs](nobs.sas) 16 | [nvalid](nvalid.sas) [parsem](parsem.sas) [qcatx](qcatx.sas) [qcompbl](qcompbl.sas) 17 | [qlist](qlist.sas) [qlowcase](qlowcase.sas) [qsubtrn](qsubstrn.sas) [qsysget](qsysget.sas) 18 | [squote](squote.sas) [substrn](substrn.sas) [symget](symget.sas) [tdexist](tdexist.sas) 19 | [varexist](varexist.sas) [varinfo](varinfo.sas) [xpttype](xpttype.sas) 20 | 21 | Macros that return SAS statements 22 | 23 | Macros that return SAS steps 24 | 25 | [cfmtgen](cfmtgen.sas) [contents](contents.sas) [contentv](contentv.sas) [csv2ds](csv2ds.sas) 26 | [csvfile](csvfile.sas) [dbcon](dbcon.sas) [dirtree](dirtree.sas) [ds2post](ds2post.sas) 27 | [dslist](dslist.sas) [github_include](github_include.sas) [maclist](maclist.sas) 28 | [replace_crlf](replace_crlf.sas) [safe_ds2csv](safe_ds2csv.sas) [sas2xport](sas2xport.sas) 29 | [subnet](subnet.sas) [xport2sas](xport2sas.sas) 30 | 31 | Alphabetical List - With descriptions 32 | 33 | * [bench](bench.sas) - Time interval between calls 34 | * [catx](catx.sas) - Replicate the SAS function CATX() in macro code 35 | * [cfmtgen](cfmtgen.sas) - Generate format library with code values included in decodes. 36 | * [compbl](compbl.sas) - Replicate the SAS function COMPBL() in macro code 37 | * [contents](contents.sas) - Use data step and function calls to gather contents information on a dataset 38 | * [contentv](contentv.sas) - Creates VIEW or DATASET containing the data file column attributes 39 | * [csv2ds](csv2ds.sas) - Convert a delimited text file into a SAS dataset 40 | * [csvfile](csvfile.sas) - Write SAS dataset as CSV file 41 | * [curdir](curdir.sas) - Returns (optionally changes) the current working directory physical name 42 | * [dbcon](dbcon.sas) - Summarize the contents of a dataset 43 | * [dblibchk](dblibchk.sas) - Return DBTYPE, DBHOST and DBNAME for an existing SAS/Access library 44 | * [direxist](direxist.sas) - Test if directory exists 45 | * [dirtree](dirtree.sas) - Build dataset of files in directory tree(s) 46 | * [dquote](dquote.sas) - Quote string with double quote characters 47 | * [ds2post](ds2post.sas) - Generate data step suitable for on-line posting to create an existing dataset 48 | * [dslist](dslist.sas) - Generate dataset list to summarize SAS datasets in libraries 49 | * [fileref](fileref.sas) - Verify whether a fileref has been assigned 50 | * [fread](fread.sas) - Reads file using only macro code 51 | * [github_include](github_include.sas) - Run SAS code from github repository 52 | * [lowcase](lowcase.sas) - Replacement for SAS supplied LOWCASE macro that eliminates errors 53 | * [macdelete](macdelete.sas) - Remove compiled macros using %SYSMACDELETE macro statement 54 | * [maclist](maclist.sas) - Generate list of compiled macros and their source directories 55 | * [missing](missing.sas) - Return current MISSING statement settings 56 | * [mvartest](mvartest.sas) - Test for the existence of a macro variable with optional scope limitation 57 | * [nobs](nobs.sas) - Return the number of observations in a dataset reference 58 | * [nvalid](nvalid.sas) - A function style macro that extends the NVALID() SAS function 59 | * [parmv](parmv.sas) - Parameter validation with standard error message generation 60 | * [parsem](parsem.sas) - Macro tool for parsing a macro variable text string 61 | * [qcatx](qcatx.sas) - Mimic CATX() function as a macro function. Return results with macro quoting 62 | * [qcompbl](qcompbl.sas) - Compress multiple spaces into one, return value with macro quoting 63 | * [qlist](qlist.sas) - Adds quotes to each word in a list 64 | * [qlowcase](qlowcase.sas) - Convert to lowercase, return results macro quoted 65 | * [qsubtrn](qsubstrn.sas) - Subset string (simulation of SUBSTRN function), return results macro quoted 66 | * [qsysget](qsysget.sas) - Get macro quoted value of enviroment variable 67 | * [replace_crlf](replace_crlf.sas) - Replace carriage return or linefeed characters that are inside quotes 68 | * [safe_ds2csv](safe_ds2csv.sas) - Write SAS dataset as CSV file insuring proper quoting 69 | * [sas2xport](sas2xport.sas) - Generate SAS V5 or V9 Transport file from SAS datasets/views 70 | * [squote](squote.sas) - Quote string with single quote characters 71 | * [subnet](subnet.sas) - Build connected subnets from pairs of nodes 72 | * [substrn](substrn.sas) - Subset string (simulation of SUBSTRN function) 73 | * [symget](symget.sas) - Get value for macro variable that is hidden by local macro variable 74 | * [syslput612](syslput612.sas) - Copy of old SAS 6.12 autocall macro %SYSLPUT() 75 | * [tdexist](tdexist.sas) - Return type of a teradata table or view to test if it exists 76 | * [varexist](varexist.sas) - Check for the existence of a specified variable 77 | * [varinfo](varinfo.sas) - Return information for a variable 78 | * [xport2sas](xport2sas.sas) - Convert SAS XPORT file to SAS dataset(s) 79 | * [xpttype](xpttype.sas) - Check file to see what type of transport file it is 80 | 81 | 82 | -------------------------------------------------------------------------------- /bench.sas: -------------------------------------------------------------------------------- 1 | %macro bench 2 | /*---------------------------------------------------------------------- 3 | Measures elapsed time (in seconds) between sucessive invocations 4 | ----------------------------------------------------------------------*/ 5 | (mvar /* Macro variable used for recording start time (default=_bench)*/ 6 | ); 7 | /*---------------------------------------------------------------------- 8 | Call it once to start the timing and then a second time to report the 9 | elapsed time and clear the saved time. 10 | 11 | Use different values for MVAR to time multiple overlapping periods. 12 | ----------------------------------------------------------------------*/ 13 | %if 0=%length(&mvar) %then %let mvar=_BENCH; 14 | %else %if ^%sysfunc(nvalid(&mvar,v7)) %then %do; 15 | %put ERROR: Macro &sysmacroname user error. 16 | &=mvar is not a valid macro variable name.; 17 | %return; 18 | %end; 19 | %if ^%symexist(&mvar) %then %global &mvar; 20 | 21 | %if (&&&mvar =) %then %let &mvar = %sysfunc(datetime()); 22 | %else %do; 23 | %put NOTE: %upcase(&mvar) Elapsed seconds = 24 | %sysevalf(%sysfunc(datetime()) - &&&mvar); 25 | %let &mvar =; 26 | %end; 27 | %mend bench; 28 | -------------------------------------------------------------------------------- /catx.sas: -------------------------------------------------------------------------------- 1 | %macro catx(dlm) /parmbuff; 2 | /*--------------------------------------------------------------------------- 3 | Mimic CATX() function as a macro function. 4 | 5 | The CAT... series of functions do not work well with %SYSFUNC() because they 6 | can accept either numeric or character values. So %SYSFUNC() has to try 7 | and figure out if the value you passed is a number or a string. Which can 8 | cause unwanted messages in the LOG and worse. 9 | 10 | Example issue with %sysfunc(catx()): 11 | 12 | 1 %put |%sysfunc(catx(^,a,,c))|; 13 | ERROR: %SYSEVALF function has no expression to evaluate. 14 | |a^c| 15 | 16 | This function will instead use %SCAN() to pull out the individual words and 17 | only insert the delimiter string when the word value is not null. 18 | 19 | It uses the PARMBUFF option to accept a virtually unlimited number of inputs. 20 | It then uses %SCAN() to pull out each word and emit it when not empty. 21 | 22 | Examples: 23 | Simple examples without any special characters: 24 | 1 %put |%catx(^,a,b,c)|; 25 | |a^b^c| 26 | 2 %put |%catx(^,a,,c)|; 27 | |a^c| 28 | 3 %put |%catx(^,,b,)|; 29 | |b| 30 | 31 | You can use simple macro quoting for the delimiter value: 32 | 4 %put |%catx(%str( and ),a,b,)|; 33 | |a and b| 34 | 5 %put |%catx(%str(;),a,b,c)|; 35 | |a;b;c| 36 | 37 | For the individual words add %NRSTR() around the quoted value: 38 | 6 %put |%catx(^,a,%nrstr(%str( b )),c)|; 39 | |a^ b ^c| 40 | 41 | ---------------------------------------------------------------------------*/ 42 | %local i sep word; 43 | %if %length(&syspbuff)>2 %then %do; 44 | %let syspbuff=%qsubstr(&syspbuff,2,%length(&syspbuff)-2); 45 | %do i=2 %to %sysfunc(countw(&syspbuff,%str(,),qm)); 46 | %let word=%scan(&syspbuff,&i,%str(,),qm); 47 | %if %length(&word) %then %do; 48 | %*;&sep.&word. 49 | %let sep=&dlm.; 50 | %end; 51 | %end; 52 | %end; 53 | %mend catx; 54 | -------------------------------------------------------------------------------- /cfmtgen.sas: -------------------------------------------------------------------------------- 1 | %macro cfmtgen 2 | /*---------------------------------------------------------------------- 3 | Generate C-format in catalog &libref..CFORMATS from info in 4 | &libref.FORMATS. 5 | ----------------------------------------------------------------------*/ 6 | (format /* List of format names */ 7 | ,libref=WORK /* Library reference for format catalogs */ 8 | ,data= /* PROC FORMAT output data set */ 9 | ); 10 | 11 | /*---------------------------------------------------------------------- 12 | This code was developed by HOFFMAN CONSULTING as part of a FREEWARE 13 | macro tool set. Its use is restricted to current and former clients of 14 | HOFFMAN CONSULTING as well as other professional colleagues. Questions 15 | and suggestions may be sent to TRHoffman@sprynet.com. 16 | ----------------------------------------------------------------------- 17 | Usage: 18 | 19 | %* creates C-formats for _WGTUNT and INVEST; 20 | 21 | %cfmtgen($WGTUNT INVEST,libref=FORMATS) 22 | 23 | %* create C-formats for formats contained in FMTOUT data set. Useful as 24 | second step in format creation ; 25 | 26 | %cfmtgen(data=FMTOUT,libref=FORMATS) 27 | ------------------------------------------------------------------------ 28 | Notes: 29 | 30 | Normally the CFMTGEN macro is invoked by the FMTGEN macro (when cfmt=1). 31 | 32 | C-formats map a code into the code plus a label. For example, the 33 | C-format for the format that maps 1 into 'MALE' would map 1 into 34 | '1 MALE'. These formats are useful to programmers who need to use the 35 | code in a program, but also require knowledge of the description. 36 | 37 | The FMTSEARCH option can be used to switch between C-formats and 38 | regular formats. 39 | 40 | Do not generate C-formats for formats that include a range of values. 41 | ----------------------------------------------------------------------- 42 | History: 43 | 44 | 21FEB96 TRHoffman creation 45 | 08NOV97 TRHoffman Protected against numeric to character conversions. 46 | 10OCT00 TRHoffman Cleaned up work data sets. 47 | ----------------------------------------------------------------------*/ 48 | %local 49 | parmerr macro /* required by parmv */ 50 | dsid varid /* used by OPEN and VARNUM SCL functions */ 51 | stype /* variable START data type (N or C) */ 52 | ltype /* variable LABEL data type (N or C) */ 53 | type /* =0 if variable TYPE not in input data set */ 54 | hlo /* =0 if variable HLO not in input data set */ 55 | ; 56 | %let macro = CFMTGEN; 57 | 58 | %parmv(LIBREF,_req=1) 59 | 60 | %if (&parmerr) %then %goto quit; 61 | 62 | %*---------------------------------------------------------------------- 63 | Use PROC FORMAT to unload requested formats when the data parameter is 64 | not specified. 65 | 66 | Exclude picture formats (TYPE = P) and informats (TYPE = I and J). 67 | Select specified entries. 68 | -----------------------------------------------------------------------; 69 | %if ^%length(&data) %then %do; 70 | %let type = type; 71 | %let hlo = 1; 72 | %let stype = C; 73 | proc format cntlout=_cfmt(where=(type in ('N' 'C')) 74 | keep=fmtname type hlo start label) 75 | lib=&libref..formats; 76 | %if (%length(&format)) %then 77 | select &format 78 | ; 79 | ; 80 | run; 81 | %end; 82 | 83 | %*---------------------------------------------------------------------- 84 | Otherwise sort the input data set in the same order as the CNTLOUT data 85 | set. Exclude picture formats and informats. 86 | 87 | Look up variable type of START and LABEL variables. 88 | -----------------------------------------------------------------------; 89 | %else %do; 90 | %let dsid = %sysfunc(open(&data)); 91 | %let hlo = %sysfunc(varnum(&dsid,hlo)); 92 | %if (&hlo) %then %let hlo = hlo; 93 | %else %let hlo =; 94 | %let type = %sysfunc(varnum(&dsid,type)); 95 | %if (&type) %then %let type = type; 96 | %else %let type =; 97 | %let varid = %sysfunc(varnum(&dsid,start)); 98 | %let stype = %sysfunc(vartype(&dsid,&varid)); 99 | %let varid = %sysfunc(varnum(&dsid,label)); 100 | %let ltype = %sysfunc(vartype(&dsid,&varid)); 101 | %let dsid = %sysfunc(close(&dsid)); 102 | 103 | proc sort data=&data out=_cfmt(keep=fmtname &type &hlo start label); 104 | %if (&type = type) %then 105 | where upcase(type) in ('N' 'C'); 106 | ; 107 | by descending &type fmtname; 108 | run; 109 | %end; 110 | 111 | %*---------------------------------------------------------------------- 112 | Determine the length of the data in the START variable. 113 | Select specified members. 114 | 115 | When START is numeric, convert to character. 116 | -----------------------------------------------------------------------; 117 | data _length(keep=fmtname &type length); 118 | set _cfmt; 119 | %if (&hlo = hlo) %then 120 | where (hlo = '') 121 | ; 122 | %else %if (&stype = C) %then 123 | where (start ^= :'**OTHER') 124 | ; ; 125 | %if (&stype = N) %then 126 | length = length(left(put(start,32.))) 127 | ; 128 | %else 129 | length = length(left(start)) 130 | ; ; 131 | run; 132 | 133 | %*---------------------------------------------------------------------- 134 | Determine maximum length. 135 | Exclude 'OTHER' from start length calculations. 136 | -----------------------------------------------------------------------; 137 | proc summary data=_length nway; 138 | by descending &type fmtname; 139 | var length; 140 | output out=_mlength(keep=&type fmtname length) max=length; 141 | run; 142 | 143 | %*---------------------------------------------------------------------- 144 | Add start value to beginning of each label. Use maximum length of 145 | start to right align start value. 146 | -----------------------------------------------------------------------; 147 | data _cfmt(rename=(_start=start _label=label)); 148 | length _start _label $200; 149 | merge _cfmt 150 | _mlength; 151 | by descending &type fmtname; 152 | drop start label length; 153 | %if (<ype = N) %then 154 | _label = left(put(label,32.)) 155 | ; 156 | %else 157 | _label = label 158 | ; ; 159 | 160 | %if (&stype = N) %then 161 | _start = left(put(start,32.)) 162 | ; 163 | %else 164 | _start = start 165 | ; ; 166 | 167 | _label = right(putc(left(_start),'$'||left(put(length,2.)))) 168 | ||' '||_label; 169 | run; 170 | 171 | proc format cntlin=_cfmt lib=&libref..cformats; 172 | run; 173 | 174 | proc sql; 175 | drop table _cfmt; 176 | drop table _length; 177 | drop table _mlength; 178 | quit; 179 | 180 | %quit: 181 | %mend cfmtgen; 182 | -------------------------------------------------------------------------------- /compbl.sas: -------------------------------------------------------------------------------- 1 | %macro compbl / parmbuff; 2 | /*---------------------------------------------------------------------------- 3 | Compress multiple spaces into one, return value without macro quoting. 4 | 5 | To avoid macro quoting issues use %QCOMPBL() to generate result with 6 | macro quoting and pass it throug %UNQUOTE() to remove the macro quoting. 7 | ----------------------------------------------------------------------------*/ 8 | %unquote(%qcompbl&syspbuff) 9 | %mend; 10 | -------------------------------------------------------------------------------- /contents.sas: -------------------------------------------------------------------------------- 1 | %macro contents 2 | /*---------------------------------------------------------------------------- 3 | Use data step and function calls to gather contents information on a dataset. 4 | -----------------------------------------------------------------------------*/ 5 | (data /* Name of dataset to inspect (default=&syslast) */ 6 | ,out /* Name of dataset to store results (default=DATAnn) */ 7 | ,append=0 /* If &OUT exists then append to it */ 8 | ); 9 | /*---------------------------------------------------------------------------- 10 | Creates a CONTENTS dataset using ATTR/ATTRC/VARxxx function calls. 11 | 12 | The resulting dataset has a combination of the metadata available from 13 | PROC CONTENTS and DICTIONARY.COLUMNS. 14 | 15 | %CONTENTS() can support dataset options. Such as RENAME= and WHERE=. 16 | If you DROP a variable that is not at the end there will be a note in 17 | the log. 18 | 19 | The NOBS value does not consider values of FIRSTOBS= or OBS= dataset options 20 | so the result is that it will be an overcount if those options are used. 21 | 22 | Using WHERE= option could result in it taking a long time as the ATTRN() 23 | function call will query the data to count the number of observations. 24 | 25 | These variablee either appear in only one of PROC CONTENTS or 26 | DICTIONARY.COLUMNS or they have the same definition in both: 27 | 28 | LIBNAME, MEMNAME, MEMLABEL, NVAR, NOBS, MEMTYPE, TYPEMEM, CRDATE, MODATE 29 | VARNUM, NAME, LENGTH, LABEL, FORMATL, FORMATD, INFORML, INFORMD 30 | 31 | These variables use the DICTIONARY.COLUMNS definition: 32 | TYPE is character: num/char (for numeric version see TYPEN) 33 | FORMAT has the full format specification (not just the name); 34 | INFORMAT has the full informat specification (not just the name); 35 | 36 | These variables are unique to %CONTENTS() output: 37 | FORMATN has format name (like FORMAT in PROC CONTENTS output) 38 | INFORMN has informat name (like INFORMAT in PROC CONTENTS output) 39 | TYPEN has variable type as a numeric: 1/2 (like TYPE in PROC CONTENTS output) 40 | TYPEF is the category returned by the 'cat' property of FMTINFO() function. 41 | Example values: num/char/date/datetime/time/curr/binary/UNKNOWN 42 | 43 | -----------------------------------------------------------------------------*/ 44 | %local did ; 45 | %*---------------------------------------------------------------------------- 46 | Default DATA to &SYSLAST and check that it can be opened. 47 | -----------------------------------------------------------------------------; 48 | %if 0=%length(&data) %then %let data=&syslast; 49 | %if (%qupcase(&data) = _NULL_) %then %do; 50 | %let sysrc=1; 51 | %put ERROR: &sysmacroname: Cannot get attributes for _NULL_ dataset. ; 52 | %return; 53 | %end; 54 | %let did=%sysfunc(open(&data)); 55 | %if 0=&did %then %do; 56 | %put ERROR: &sysmacroname: Unable to open &=data..; 57 | %return; 58 | %end; 59 | %let did=%sysfunc(close(&did)); 60 | 61 | %*---------------------------------------------------------------------------- 62 | Use DATA step to create the contents information. 63 | -----------------------------------------------------------------------------; 64 | data &out; 65 | %if &append=1 and %length(&out) %then %do; 66 | %if %sysfunc(exist(&out)) %then %do; 67 | %*---------------------------------------------------------------------------- 68 | Use MODIFY to append to an existing dataset when APPEND=1. 69 | -----------------------------------------------------------------------------; 70 | if 0 then modify &out; 71 | %end; 72 | %end; 73 | length 74 | libname $8 memname $32 75 | varnum 8 name $32 length 8 typen 8 type $4 typef $8 76 | format $49 informat $49 77 | formatn $32 formatl formatd 8 78 | informn $32 informl informd 8 79 | label $256 80 | nvar 8 nobs 8 crdate 8 modate 8 81 | typemem $8 memtype $8 82 | memlabel $256 83 | ; 84 | format crdate modate datetime19.; 85 | dsid=open(symget('data')); 86 | %*---------------------------------------------------------------------------- 87 | Read member information using ATTRN() and ATTRC() functions. 88 | -----------------------------------------------------------------------------; 89 | nvar=attrn(dsid,'nvars'); 90 | nobs=attrn(dsid,'nlobsf'); 91 | crdate=attrn(dsid,'crdte'); 92 | modate=attrn(dsid,'modte'); 93 | libname=attrc(dsid,'lib'); 94 | memname=attrc(dsid,'mem'); 95 | memlabel=attrc(dsid,'label'); 96 | typemem=attrc(dsid,'type'); 97 | memtype=attrc(dsid,'mtype'); 98 | %*---------------------------------------------------------------------------- 99 | Loop until found all of the variables. Skip variables where VARNAME() is 100 | empty. That is caused by DROP=/KEEP= dataset options. 101 | -----------------------------------------------------------------------------; 102 | do index=1 by 1 while (varnum0 %then %if %sysfunc(fileexist(&file)) %then 129 | %let file=%sysfunc(quote(%qsysfunc(dequote(&file)),%str(%'))) 130 | ; 131 | %else %parmv(FILEN,_msg=File not found); 132 | %end; 133 | %parmv(OUT,_case=n) 134 | %parmv(GETNAMES,_val=0 1,_def=1) 135 | %parmv(namerow,_val=nonnegative) 136 | %parmv(datarow,_val=nonnegative) 137 | %if %qupcase("&guessingrows")="MAX" %then %let guessingrows=0; 138 | %else %parmv(GUESSINGROWS,_val=nonnegative,_def=0); 139 | %parmv(PERCENT,_val=positive) 140 | %parmv(OBS,_val=positive) 141 | %parmv(REPLACE,_val=0 1) 142 | %parmv(OVERRIDES) 143 | %parmv(MAXCHAR,_val=positive,_def=32767) 144 | %parmv(RUN,_val=0 1,_def=1) 145 | %parmv(MISSING) 146 | 147 | %*---------------------------------------------------------------------------- 148 | Check DLM parameter and convert to string usable in INFILE statement. 149 | -----------------------------------------------------------------------------; 150 | %if %length(&dlm)=3 %then %let dlm=%qsysfunc(dequote(&dlm)); 151 | %if %length(&dlm)=0 %then %let dlm=','; 152 | %else %if %length(&dlm)=1 %then %let dlm=%unquote(%bquote('&dlm')); 153 | %else %if %length(&dlm)=2 and 154 | 0=%sysfunc(verify(%upcase(&dlm),0123456789ABCDEF)) %then %do; 155 | %let dlm=%unquote(%bquote('&dlm'x)); 156 | %end; 157 | %else %if (%qupcase(&dlm)=TAB) %then %let dlm='09'x; 158 | %else %if (%qupcase(&dlm)=COMMA) %then %let dlm=','; 159 | %else %if (%qupcase(&dlm)=PIPE) %then %let dlm='|'; 160 | %else %if (%qupcase(&dlm)=SEMICOLON) %then %let dlm=';'; 161 | %else %parmv(DLM,_msg=Valid values include: a single character%str(,) 162 | two digit hexcode%str(,) 163 | keyword: TAB COMMA PIPE or SEMICOLON) 164 | ; 165 | 166 | %if (&parmerr) %then %goto quit; 167 | 168 | %if %sysfunc(verify("&missing","_ABCDEFGHIJKLMNOPRSTUVWXYZ")) %then 169 | %parmv(MISSING,_msg=Only valid characters are _ABCDEFGHIJKLMNOPRSTUVWXYZ) 170 | ; 171 | %if %length(&percent) %then %if 100<&percent %then 172 | %parmv(PERCENT,_msg=Value must be between 1 and 100) 173 | ; 174 | %if %length(&overrides) %then %if not %sysfunc(exist(&overrides)) %then 175 | %parmv(OVERRIDES,_msg=Dataset not found) 176 | ; 177 | %if %sysfunc(exist(&out)) and "&replace"="0" and "&run"="1" %then 178 | %parmv(_msg=Import cancelled. Output dataset &out already exists. 179 | Specify REPLACE option to overwrite it. Or set RUN=no) 180 | ; 181 | %if (&parmerr) %then %goto quit; 182 | 183 | %*---------------------------------------------------------------------------- 184 | Default NAMEROW and DATAROW based on GETNAMES option 185 | Create NAMEOBS and DATAOBS macro variables with FIRSTOBS= and OBS= values to 186 | make code generation easier. 187 | -----------------------------------------------------------------------------; 188 | %if ^%length(&namerow) %then %let namerow=&getnames; 189 | %if ^%length(&datarow) %then %let datarow=%eval(&namerow+1); 190 | %let nameobs=firstobs=&namerow obs=&namerow; 191 | %let dataobs=firstobs=&datarow; 192 | %if %length(&obs) %then %let dataobs=&dataobs obs=%eval(&datarow+&obs-1); 193 | 194 | *----------------------------------------------------------------------------; 195 | * Save current MISSING statement settings and issue new MISSING statement ; 196 | *----------------------------------------------------------------------------; 197 | data _null_; 198 | missing='_ABCDEFGHIJKLMNOPQRSTUVWXYZ'; 199 | do i=1 to 27; 200 | if .=input(char(missing,i),??1.) then substr(missing,i,1)=' '; 201 | end; 202 | call symputx('misssave',compress(missing,' ')); 203 | run; 204 | missing &missing; 205 | 206 | *----------------------------------------------------------------------------; 207 | * Read the header row of the delimited file as labels ; 208 | *----------------------------------------------------------------------------; 209 | data _names_; 210 | length varnum 8 name $32 label $256 ; 211 | %if (&getnames) %then %do; 212 | infile &file &fileopt dsd dlm=&dlm &nameobs ; 213 | varnum+1; 214 | retain name ' '; 215 | input label @@ ; 216 | %end; 217 | %else %do; 218 | stop; 219 | call missing(of _all_); 220 | %end; 221 | run; 222 | 223 | *----------------------------------------------------------------------------; 224 | * Generate _VALUES_ view with row, varnum, length, and first 40 bytes ; 225 | *----------------------------------------------------------------------------; 226 | %if %sysfunc(exist(_values_)) %then %do; 227 | proc sql; drop table _values_; quit; 228 | %end; 229 | data _values_(keep=row varnum length short) / view=_values_; 230 | infile &file &fileopt dsd dlm=&dlm truncover length=ll column=cc &dataobs ; 231 | length row varnum length 8 short $40 value $&maxchar ; 232 | row+1; 233 | input @; 234 | %if %length(&percent) %then %do; 235 | if rand('Uniform')<=%sysfunc(putn(&percent/100,4.2)) ; 236 | %end; 237 | do varnum=1 by 1 while(cc<=ll); 238 | start=cc; 239 | input short @ ; 240 | length=cc-start-1; 241 | if 0=length then continue; 242 | if length<=40 then length=lengthn(short); 243 | else do; 244 | input @start value @ ; 245 | len2=lengthn(value); 246 | if len2 < %eval(&maxchar-1) then length=len2; 247 | end; 248 | if length then output; 249 | end; 250 | %if (&guessingrows) %then %do; 251 | if row >= &guessingrows then stop; 252 | %end; 253 | run; 254 | 255 | *----------------------------------------------------------------------------; 256 | * Summarize values and determine best fit xtype and appropriate informats or ; 257 | * formats to attach to the variables. ; 258 | *----------------------------------------------------------------------------; 259 | proc sql ; 260 | create table _types_ as 261 | select coalesce(a.varnum,b.varnum) as varnum 262 | , a.name 263 | , case 264 | when (missing(b.nonmiss)) then 'empty' 265 | when (b.maxlength in (1:15) and b.nonmiss=b.integer) then 'integer' 266 | when (b.nonmiss=b.integer) then 'character' 267 | when (b.maxlength in (1:32) and b.nonmiss=b.numeric) then 'numeric' 268 | when (b.maxlength in (1:32) and b.nonmiss=b.comma) then 'comma' 269 | when (b.maxlength in (1:40) and b.nonmiss=b.datetime and b.time ne b.nonmiss) then 'datetime' 270 | when (b.maxlength in (1:32) and b.nonmiss=b.date) then 'date' 271 | when (b.maxlength in (1:10) and b.nonmiss=b.yymmdd) then 'yymmdd' 272 | when (b.maxlength in (1:10) and b.nonmiss=b.mmddyy) then 'mmddyy' 273 | when (b.maxlength in (1:10) and b.nonmiss=b.ddmmyy) then 'ddmmyy' 274 | when (b.maxlength in (1:35) and b.nonmiss=b.e8601dz) then 'e8601dz' 275 | when (b.maxlength in (1:40) and b.nonmiss=b.anydtdtm and b.nonmiss ne b.time) then 'anydtdtm' 276 | when (b.maxlength in (1:32) and b.nonmiss=b.time) then 'time' 277 | else 'character' 278 | end as xtype length=9 279 | , case when calculated xtype in ('empty' 'character') then 'char' 280 | else 'num' 281 | end as type length=4 282 | , case when calculated xtype='empty' then '$1' 283 | when calculated xtype ='character' then cats('$',max(1,b.maxlength)) 284 | else '8' 285 | end as length length=6 286 | , case when calculated xtype in ('integer' 'numeric' 'character' 'empty') then ' ' 287 | else cats(calculated xtype,'.') 288 | end as informat length=43 289 | , case 290 | when calculated xtype in ('character' 'empty') then ' ' 291 | when calculated xtype='integer' then 292 | case when b.maxlength>1 and char(b.min,1)='0' and b.maxlength=b.minlength 293 | then cats('z',maxlength,'.') 294 | when b.maxlength>12 then cats('f',maxlength,'.') 295 | else ' ' 296 | end 297 | when calculated xtype='numeric' then 298 | case when b.maxlength>12 then cats('best',maxlength,'.') else ' ' end 299 | when calculated xtype='datetime' then cats('datetime',max(b.maxlength,19),'.') 300 | when calculated xtype='date' then 'date9.' 301 | when calculated xtype in ('anydtdtm') then 'datetime19.' 302 | when calculated xtype in ('mmddyy','ddmmyy','yymmdd') then cats(calculated xtype,'10.') 303 | else cats(calculated xtype,b.maxlength,'.') 304 | end as format length=43 305 | , a.label length=256 306 | , b.minlength 307 | , b.maxlength 308 | , b.min 309 | , b.max 310 | , coalesce(b.nonmiss,0) as nonmiss 311 | , b.numeric 312 | , b.comma 313 | , b.integer 314 | , b.date 315 | , b.datetime 316 | , b.time 317 | , b.yymmdd 318 | , b.mmddyy 319 | , b.ddmmyy 320 | , b.e8601dz 321 | , b.anydtdtm 322 | from _names_ a full join 323 | ( select varnum 324 | , count(*) as nonmiss 325 | , min(length) as minlength 326 | , max(length) as maxlength 327 | , sum(case when length<=32 then (. ne input(short,?32.)) else 0 end) 328 | as numeric 329 | , sum(case when length<=32 then (. ne input(compress(short,'($,)'),?32.)) else 0 end) 330 | as comma 331 | , sum(case when length<=32 and not missing(input(short,?32.)) 332 | then int(input(short,?32.))= input(short,?32.) 333 | else 0 end) 334 | as integer 335 | , sum(case when length<=32 then not missing(input(short,?date32.)) else 0 end) 336 | as date 337 | , sum(case when length<=40 then not missing(input(short,?datetime40.)) else 0 end) 338 | as datetime 339 | , sum(case when length<=32 and indexc(short,':') in (2 3) then not missing(input(short,?time32.)) else 0 end) 340 | as time 341 | , sum(case when length<=10 then not missing(input(short,?yymmdd10.)) else 0 end) 342 | as yymmdd 343 | , sum(case when length<=10 then not missing(input(short,?mmddyy10.)) else 0 end) 344 | as mmddyy 345 | , sum(case when length<=10 then not missing(input(short,?ddmmyy10.)) else 0 end) 346 | as ddmmyy 347 | , sum(case when length<=35 then not missing(input(short,?e8601dz35.)) else 0 end) 348 | as e8601dz 349 | , sum(case when length<=40 then not missing(input(short,?anydtdtm40.)) else 0 end) 350 | as anydtdtm 351 | , min(short) as min 352 | , max(short) as max 353 | from _values_ 354 | group by varnum 355 | ) b 356 | on a.varnum = b.varnum 357 | order by 1 358 | ; 359 | quit; 360 | 361 | *----------------------------------------------------------------------------; 362 | * Apply overrides to the generated metadata ; 363 | * Generate unique names ; 364 | *----------------------------------------------------------------------------; 365 | data _types_; 366 | %if %length(&overrides) %then %do; 367 | update _types_ &overrides end=eof; 368 | %end; 369 | %else %do; 370 | set _types_ end=eof; 371 | %end; 372 | by varnum; 373 | length upcase $32 firstvar suffix 8; 374 | if _n_=1 then do; 375 | declare hash u(); 376 | u.definekey('upcase'); 377 | u.definedata('firstvar'); 378 | u.definedone(); 379 | declare hash v(dataset:'_types_',ordered:'Y'); 380 | v.definekey('varnum'); 381 | v.definedata(all:'Y'); 382 | v.definedone(); 383 | declare hiter iter('v'); 384 | end; 385 | if last.varnum; 386 | upcase=cats('VAR',varnum); 387 | %if %sysfunc(getoption(validvarname)) = ANY %then %do; 388 | name=coalescec(name,label,upcase); 389 | %end; 390 | %else %do; 391 | if nvalid(coalescec(name,label,upcase)) then name=coalescec(name,label,upcase); 392 | else do; 393 | * Replace adjacent non-valid characters with single underscore ; 394 | name=translate(trim(prxchange('s/([^a-zA-Z0-9]+)/ /',-1,coalescec(name,label,upcase))),' _','_ '); 395 | name=prxchange('s/(^[0-9])/_$1/',1,name); 396 | end; 397 | %end; 398 | upcase=upcase(name); 399 | %if %sysfunc(getoption(validvarname)) eq UPCASE %then %do; 400 | name=upcase; 401 | %end; 402 | v.replace(); 403 | firstvar=varnum; 404 | if u.add() then ndups+1; 405 | if eof then do; 406 | *----------------------------------------------------------------------------; 407 | * Wait till all unique names have been identified before looping over the ; 408 | * variables again to generate unique names for dups. Write to _TYPES_ ; 409 | *----------------------------------------------------------------------------; 410 | rc=iter.first(); 411 | do while (rc=0); 412 | upcase=upcase(name); 413 | u.find(); 414 | suffix=0; 415 | if varnum ne firstvar then do; 416 | firstvar=varnum; 417 | do suffix=0 by 1 while(u.add()); 418 | upcase=cats(substr(upcase,1,31-length(cats(suffix+1))),'_',suffix+1); 419 | end; 420 | end; 421 | if suffix>0 then name 422 | = cats(substr(name,1,length(upcase)-length(cats(suffix))-1),'_',suffix) 423 | ; 424 | if name=label then label=' '; 425 | if maxlength > &maxchar and type='char' then do; 426 | length="$&maxchar"; 427 | put 'WARNING: Column ' varnum +(-1) ', ' name :$quote. ', might be ' 428 | 'truncated. Setting length to ' length 'but longest value seen ' 429 | 'was ' maxlength :comma20. 'bytes long.' 430 | ; 431 | end; 432 | output; 433 | rc=iter.next(); 434 | end; 435 | end; 436 | drop upcase firstvar suffix ; 437 | run; 438 | 439 | *----------------------------------------------------------------------------; 440 | * Generate data step code ; 441 | * - For each statement use value of variable named the same as the statement.; 442 | * - Only variables that are required in that statement are generated. ; 443 | * - For LABEL statement use = between name and value instead of space. ; 444 | * - Wrap statements when lines get too long. ; 445 | * - Eliminate statements when no variables required that statement. ; 446 | *----------------------------------------------------------------------------; 447 | filename _code_ temp; 448 | data _null_; 449 | file _code_ column=cc ; 450 | set _types_ (obs=1) ; 451 | retain input ' '; 452 | length statement $10 string $370 ; 453 | 454 | put "data &out;" ; 455 | put @3 'infile ' @ ; 456 | do string=%sysfunc(quote(&file &fileopt)),"dlm=&dlm",'dsd','truncover',"&dataobs"; 457 | if 75 < cc+lengthn(string) then do; 458 | anysplit=1; 459 | put / @5 @; 460 | end; 461 | if lengthn(string) then put string @; 462 | end; 463 | if anysplit then put / @3 @; 464 | put ';'; 465 | 466 | do statement='length','informat','format','label'; 467 | call missing(any,anysplit); 468 | put @3 statement @ ; 469 | do p=1 to nobs ; 470 | set _types_ point=p nobs=nobs ; 471 | string=vvaluex(statement); 472 | if not missing(string) then do; 473 | any=1; 474 | if statement ne 'label' then string=catx(' ',nliteral(name),string); 475 | else string=catx('=',nliteral(name),quote(trim(string),"'")); 476 | if 75<(cc+length(string)) then do; 477 | anysplit=1; 478 | put / @5 @ ; 479 | end; 480 | put string @ ; 481 | end; 482 | end; 483 | if anysplit then put / @3 @ ; 484 | if not any then put @1 10*' ' @1 @ ; 485 | else put ';' ; 486 | end; 487 | p=1; 488 | set _types_ point=p ; 489 | string=nliteral(name); 490 | put @3 'input ' string '-- ' @; 491 | p=nobs; 492 | set _types_ point=p ; 493 | string=nliteral(name); 494 | put string ';' ; 495 | put 'run;' ; 496 | run; 497 | 498 | %if (&run) %then %do; 499 | *----------------------------------------------------------------------------; 500 | * Run the generated data step ; 501 | *----------------------------------------------------------------------------; 502 | %let optsave=%sysfunc(getoption(mprint)); 503 | options nomprint; 504 | %include _code_ / source2 ; 505 | options &optsave; 506 | %end; 507 | %else %do; 508 | *----------------------------------------------------------------------------; 509 | * Show the generated data step code in the log ; 510 | *----------------------------------------------------------------------------; 511 | data _null_; 512 | infile _code_; 513 | input; 514 | put _infile_; 515 | run; 516 | %end; 517 | 518 | *----------------------------------------------------------------------------; 519 | * Reset MISSING statement settings; 520 | *----------------------------------------------------------------------------; 521 | missing &misssave; 522 | 523 | %quit: 524 | %*---------------------------------------------------------------------------- 525 | Report on time spent in macro 526 | -----------------------------------------------------------------------------; 527 | %let dt=%sysevalf(%sysfunc(datetime())-&dt); 528 | %if &dt < 60 %then %let dt=&dt seconds; 529 | %else %let dt=%sysfunc(putn(&dt,time14.3)); 530 | %put NOTE: ¯o used (Total process time): &dt.; 531 | %mend csv2ds; 532 | -------------------------------------------------------------------------------- /csv_vnext.sas: -------------------------------------------------------------------------------- 1 | %macro csv_vnext 2 | /*---------------------------------------------------------------------- 3 | Write SAS dataset as CSV file with headers using single data step 4 | ----------------------------------------------------------------------*/ 5 | (dsn /* Input dataset name. DSNoptions allowed */ 6 | ,outfile=csv /* Output fileref or quoted filename */ 7 | ,dlm=',' /* Delimiter character as literal value */ 8 | ,names=1 /* Write header row? */ 9 | ,label=0 /* Use LABEL instead of NAME for header row? */ 10 | ,format= /* Allow overrides of formats */ 11 | ); 12 | /*---------------------------------------------------------------------- 13 | Write a delimited file in a single DATA step using CALL VNEXT() method 14 | for adding the variable names. For a more complicated method with more 15 | options and parameter checking use the %CSVFILE() macro instead. 16 | This method was originally posted online by data_null_ in many places. 17 | For example look at this thread on SAS Communities 18 | https://communities.sas.com/t5/Base-SAS-Programming/Output-to-delimited-format/m-p/292767#M60829 19 | - To pass a physical name for a file enclose it in quotes. 20 | - To pass a different delimiter use a string literal. 21 | - You can use hex literal for delimiter. '09'x is a TAB character. 22 | - To suppress header row use NAMES=0. 23 | - To use LABEL instead of NAME in header row use LABEL=1. 24 | - Do not include the FORMAT keyword in the FORMAT= option. 25 | Examples: 26 | data one; 27 | set sashelp.shoes(obs=5); 28 | run; 29 | %csv_vnext(outfile=log ls=132,label=1,dlm='^'); 30 | %csv_vnext(outfile=log ls=132,names=0,format=) 31 | filename csv temp; 32 | %csv_vnext(dsn=sashelp.shoes(obs=5)); 33 | ----------------------------------------------------------------------*/ 34 | data _null_; 35 | set &dsn; 36 | format &format; 37 | file &outfile dsd dlm=&dlm; 38 | %if (&names) %then %do; 39 | if _n_ eq 1 then link names; 40 | %end; 41 | put (_all_)(+0); 42 | %if (&names) %then %do; 43 | return; 44 | names: 45 | length __name_ $255; 46 | do while(1); 47 | call vnext(__name_); 48 | if lowcase(__name_) = '__name_' then leave; 49 | %if (&label) %then %do; 50 | __name_ = vlabelx(__name_); 51 | %end; 52 | put __name_ @; 53 | end; 54 | put; 55 | %end; 56 | run; 57 | 58 | %mend csv_vnext; 59 | -------------------------------------------------------------------------------- /csvfile.sas: -------------------------------------------------------------------------------- 1 | %macro csvfile 2 | /*---------------------------------------------------------------------- 3 | Write SAS dataset as CSV file. 4 | ----------------------------------------------------------------------*/ 5 | (dataset /* Dataset to write to CSV file. DSNoptions are allowed. */ 6 | ,outfile= /* Fileref or physical filename. (Def uses dataset name) */ 7 | ,varlist= /* Variable names to include. Variable lists are allowed */ 8 | ,dlm= /* Use single character, 2 digit hexcode, a keyword */ 9 | /* (SPACE COMMA TAB PIPE) or quoted string. (Def=comma) */ 10 | ,names=1 /* Include header row in output file? */ 11 | ,label=0 /* Use LABEL in place of NAME in header row? */ 12 | ); 13 | /*---------------------------------------------------------------------- 14 | Write SAS dataset as CSV file. 15 | 16 | If DATASET is not specified then it will use &SYSLAST. You can add 17 | dataset options (such as KEEP=, DROP= and OBS=) to the DATASET 18 | parameter. 19 | 20 | If OUTFILE is not specified then name will be built by adding a csv 21 | suffix to the membername of the input dataset. If the value supplied is 22 | a fileref then it will write to that fileref. Otherwise if &OUTFILE is 23 | not quoted then quotes are added so it can be used in the FILE 24 | statement. 25 | 26 | VARLIST is a space delimited list of variable names to output. If you 27 | do not specify then _ALL_ variables are used. You can use variable lists 28 | such as PROC1-PROC10, a--c, prefix: in the VARLIST parameter. You can 29 | use the VARLIST parameter to control the order of the columns in the 30 | output. 31 | 32 | If you just want to limit the variables but not change the order they 33 | are written then use the KEEP= dataset option on the DATASET parameter. 34 | 35 | The DLM option can be any single character, one of the supported keywords 36 | (SPACE COMMA TAB PIPE), a two digit hexcode, or a quoted string. 37 | 38 | Example: 39 | * write last created dataset to a tab delimited file. ; 40 | %csvfile(dlm=tab); 41 | 42 | * Write first 50 observations of csv file. ; 43 | %csvfile(mydata(obs=50),outfile=sample.csv); 44 | 45 | * Write selected variables to pipe delimited file. ; 46 | %csvfile(dlm=pipe,varlist=id diag1-diag5 proc1-proc5); 47 | 48 | ----------------------------------------------------------------------*/ 49 | %local previous parmerr rc addquote dsn dsnopts dlmq; 50 | %let parmerr=0; 51 | 52 | %*---------------------------------------------------------------------- 53 | Retrieve &SYSLAST value to use as default and save so can reset it. 54 | -----------------------------------------------------------------------; 55 | %let previous=&syslast; 56 | 57 | *----------------------------------------------------------------------; 58 | * Make sure delimiter value is quoted, support keywords and hex codes.; 59 | *----------------------------------------------------------------------; 60 | data _null_; 61 | length dlm $200; 62 | dlm=coalescec(left(symget('DLM')),','); 63 | if upcase(dlm)='SPACE' then dlm=' '; 64 | else if upcase(dlm)='TAB' then dlm='09'; 65 | else if upcase(dlm)='COMMA' then dlm=','; 66 | else if upcase(dlm)='PIPE' then dlm='|'; 67 | if length(dlm)=1 then dlm=quote(trim(dlm)); 68 | else if length(dlm)=2 and not notxdigit(trim(dlm)) then 69 | dlm=quote(trim(dlm))||'x' 70 | ; 71 | else if 1 ^=indexc(dlm,'"',"'") then stop; 72 | call symputx('DLMQ',dlm); 73 | run; 74 | %if (NOT %length(&dlmq)) %then %do; 75 | %put ERROR: DLM value of %superq(dlm) not valid.; 76 | %let parmerr=1; 77 | %end; 78 | 79 | %*---------------------------------------------------------------------- 80 | Set default values for DATASET name. 81 | When _NULL_ set to "_NULL_" so that OPEN() will fail. 82 | -----------------------------------------------------------------------; 83 | %if %bquote(&dataset)= %then %let dataset=&previous; 84 | %if %qupcase(&dataset)=_NULL_ %then %let dataset="_NULL_"; 85 | 86 | %*---------------------------------------------------------------------- 87 | Test if dataset can be opened. 88 | -----------------------------------------------------------------------; 89 | %let rc=%sysfunc(open(&dataset,i)); 90 | %if &rc %then %let rc=%sysfunc(close(&rc)); 91 | %else %do; 92 | %put ERROR: Cannot open &dataset.. ; 93 | %let parmerr=1; 94 | %end; 95 | 96 | %if (&parmerr) %then %goto quit; 97 | 98 | %*---------------------------------------------------------------------- 99 | Split DATASET into DSN and DSNOPTS 100 | -----------------------------------------------------------------------; 101 | %let dsn=%scan(&dataset,1,()); 102 | %let dsnopts=%substr(&dataset%str( ),%length(&dsn)+1); 103 | %if %length(&dsnopts)>1 %then 104 | %let dsnopts=%substr(&dsnopts,2,%length(&dsnopts)-2) 105 | ; 106 | 107 | %*---------------------------------------------------------------------- 108 | Make sure that OUTFILE parameter is in proper form for FILE statement. 109 | 110 | - Default to membername with .csv suffix 111 | - Add quotes unless name starts with a quote or is a valid fileref. 112 | -----------------------------------------------------------------------; 113 | %let addquote=1; 114 | %if %bquote(&outfile)= %then 115 | %let outfile=%sysfunc(lowcase(%scan(&dsn,-1,.))).csv 116 | ; 117 | %else %if 1=%sysfunc(indexc(&outfile,'"')) %then %let addquote=0; 118 | %else %if %length(&outfile) <= 8 and %sysfunc(nvalid(&outfile)) %then 119 | %if %sysfunc(fileref(&outfile))<=0 %then %let addquote=0 120 | ; 121 | %if &addquote %then %let outfile=%sysfunc(quote(&outfile)); 122 | 123 | %*---------------------------------------------------------------------- 124 | Set default values for VARLIST parameter. 125 | -----------------------------------------------------------------------; 126 | %if %bquote(&varlist)= %then %let varlist=_all_; 127 | 128 | *----------------------------------------------------------------------; 129 | * Generate list of variable names ; 130 | *----------------------------------------------------------------------; 131 | proc transpose data=&dsn(&dsnopts obs=0) ; 132 | var &varlist; 133 | run; 134 | 135 | %if (&syserr) %then %do; 136 | %*---------------------------------------------------------------------- 137 | When PROC TRANSPOSE has trouble then generate message and skip writing. 138 | -----------------------------------------------------------------------; 139 | %put ERROR: Unable to generate variable names. &=varlist ; 140 | %let parmerr=1; 141 | %end; 142 | %else %do; 143 | *----------------------------------------------------------------------; 144 | * Initialize output file and optionally write header row ; 145 | *----------------------------------------------------------------------; 146 | data _null_; 147 | file &outfile dlm=&dlmq dsd lrecl=1000000 ; 148 | %if (&names) %then %do; 149 | length _name_ _label_ $255 ; 150 | array _ _name_ _label_; 151 | set &syslast end=eof; 152 | %if (&label) %then %do; 153 | _name_ = coalescec(_label_,_name_); 154 | %end; 155 | put _name_ @; 156 | if eof then put; 157 | %end; 158 | run; 159 | 160 | *----------------------------------------------------------------------; 161 | * Write data rows ; 162 | *----------------------------------------------------------------------; 163 | data _null_; 164 | set &dsn(&dsnopts); 165 | file &outfile mod dlm=&dlmq dsd lrecl=1000000 ; 166 | put (&varlist) (+0); 167 | run; 168 | %end; 169 | 170 | *----------------------------------------------------------------------; 171 | * Remove the variable names dataset. ; 172 | *----------------------------------------------------------------------; 173 | proc delete data=&syslast ; 174 | run; 175 | 176 | %quit: 177 | %*---------------------------------------------------------------------- 178 | Restore &SYSLAST setting. 179 | -----------------------------------------------------------------------; 180 | %let syslast=&previous; 181 | %let sysrc=&parmerr; 182 | 183 | %mend csvfile; 184 | -------------------------------------------------------------------------------- /curdir.sas: -------------------------------------------------------------------------------- 1 | %macro curdir 2 | /*---------------------------------------------------------------------- 3 | Returns (optionally changes) the current SAS directory physical name 4 | ----------------------------------------------------------------------*/ 5 | (curdir /* Optional new current directory */ 6 | ) 7 | ; 8 | /*---------------------------------------------------------------------- 9 | Usage: 10 | 11 | When a path is provided it will first change the current directory to 12 | that directory. It returns the current SAS directory physical name, 13 | after any optional change. 14 | 15 | %put %curdir is the current directory.; 16 | %put %curdir(~/sas) is the NEW current directory.; 17 | %let here=%curdir; 18 | %put Changed current directory to its parent directory %curdir(..); 19 | ------------------------------------------------------------------------ 20 | Notes: 21 | 22 | It will open a fileref to find the current directory. Using that method 23 | will not write a message to the log. 24 | 25 | But when you want to change directories it will call the DLGCDIR() 26 | function which will always write a message to the log. 27 | 28 | The macro variable SYSRC will be set. 0 means success and any other 29 | value is the error code from DLGCDIR() function call. 30 | 31 | Based on code from Tom Hoffman. 32 | ----------------------------------------------------------------------- 33 | History: 34 | 35 | 11MAR99 TRHoffman Creation - with help from Tom Abernathy. 36 | 06DEC00 TRHoffman Used . notation to reference current directory as 37 | suggested by Fan Zhou. 38 | 19MAR2023 abernt Added option to change the directory using DLGCDIR(). 39 | ----------------------------------------------------------------------*/ 40 | %local fr rc ; 41 | 42 | %*--------------------------------------------------------------------- 43 | Set the SYSRC macro variable to default as success 44 | ----------------------------------------------------------------------; 45 | %if not %symexist(sysrc) %then %global sysrc; 46 | %let sysrc=0; 47 | 48 | %*--------------------------------------------------------------------- 49 | When a path is provided use DLGCDIR() to change current directory. 50 | Pass the return code to SYSRC macro variable. 51 | ----------------------------------------------------------------------; 52 | %if %length(&curdir) %then %let sysrc=%sysfunc(dlgcdir(&curdir)); 53 | 54 | %*--------------------------------------------------------------------- 55 | Open a fileref pointing at the current directory and get its path. 56 | ----------------------------------------------------------------------; 57 | %let rc = %sysfunc(filename(fr,.)); 58 | %let curdir = %sysfunc(pathname(&fr)); 59 | %let rc = %sysfunc(filename(fr)); 60 | 61 | %*--------------------------------------------------------------------- 62 | Return the current directory as the output of the macro. 63 | ----------------------------------------------------------------------; 64 | &curdir 65 | 66 | %mend curdir; 67 | -------------------------------------------------------------------------------- /dbcon.sas: -------------------------------------------------------------------------------- 1 | %macro dbcon 2 | /*---------------------------------------------------------------------- 3 | Summarize the contents of a dataset. 4 | ----------------------------------------------------------------------*/ 5 | (data /* Dataset name */ 6 | ,maxchar=40 /* Maximum length of character variables */ 7 | ,maxobs=100000 /* Maximum observations before using sampling */ 8 | ,select= /* Variable names to select for analysis */ 9 | ,exclude= /* Variable names to exclude from analysis */ 10 | ,outval=_dbvals /* Dataset name for values output */ 11 | ,outsum=_dbvars /* Dataset name for variable summary output */ 12 | ,fname= /* Fileref or filename in quotes for text file */ 13 | ,nval=10 /* Number of distinct values to print */ 14 | ,printn=0 /* Include value frequency when text file is made */ 15 | ); 16 | /*---------------------------------------------------------------------- 17 | 18 | 19 | Calls: parmv.sas qlist.sas contentv.sas nobs.sas 20 | 21 | Usage notes: 22 | This macro will potentially generate a very large work dataset to 23 | contain all of the possible values. To reduce the size of this dataset 24 | and reduce processing time you can use these options: 25 | 26 | - Use the SELECT and/or EXCLUDE options to limit the variables that 27 | will be summarized. 28 | 29 | - Use the MAXCHAR option to truncate long character variables. 30 | 31 | - Set MAXOBS to a value > 0. This will cause macro to sample the 32 | dataset when there are more than MAXOBS observations. 33 | 34 | ----------------------------------------------------------------------*/ 35 | %local macro parmerr nobs sample varlist maxlen flen trunc anynum anychar; 36 | %let macro=&sysmacroname; 37 | %parmv(data,_req=1) 38 | %parmv(nval,_val=positive,_def=10) 39 | %parmv(maxchar,_val=nonnegative,_def=40) 40 | %parmv(exclude,_words=1) 41 | %parmv(select,_words=1) 42 | %parmv(outval,_req=1) 43 | %parmv(outsum,_req=1) 44 | %parmv(maxobs,_val=nonnegative) 45 | %parmv(printn,_val=0 1) 46 | 47 | %if (&parmerr) %then %goto quit; 48 | 49 | %nobs(&data); 50 | %if (&nobs < 0 ) %then %do; 51 | %parmv(DATA,_msg=Dataset not found) 52 | %goto quit; 53 | %end; 54 | *----------------------------------------------------------------------; 55 | * Get attributes for variables in the input dataset ; 56 | *----------------------------------------------------------------------; 57 | %contentv(&data,out=dsinfo,genfmt=0) 58 | 59 | *----------------------------------------------------------------------; 60 | * Reduce to variables of interest and find maximum length char var ; 61 | *----------------------------------------------------------------------; 62 | proc sql noprint; 63 | create table dsinfo as 64 | select libname,memname,name,varnum,type,length,format,label 65 | from dsinfo 66 | where 1 67 | %if %length(&select) %then 68 | & upcase(name) in %qlist(&select) 69 | ; 70 | %if %length(&exclude) %then 71 | & upcase(name) ^in %qlist(&exclude) 72 | ; 73 | order name 74 | ; 75 | select name into :varlist separated by ' ' from dsinfo; 76 | select max(length),max(type='num'),max(type='char') 77 | into :maxlen,:anynum,:anychar 78 | from dsinfo 79 | ; 80 | quit; 81 | %if (^%length(&varlist)) %then %do; 82 | %parmv(_msg=No variables selected from &data) 83 | %goto quit; 84 | %end; 85 | 86 | 87 | %let sample=0; 88 | %if (&maxobs and (&nobs > &maxobs)) %then %do; 89 | *----------------------------------------------------------------------; 90 | * NOTE: When dataset is large then only take a sample of the data ; 91 | *----------------------------------------------------------------------; 92 | %let sample=%sysfunc(ceil(&nobs/&maxobs)); 93 | %put NOTE: &data has &nobs observations. This macro will only read 94 | every &sample.th observation.; 95 | %end; 96 | 97 | %*--------------------------------------------------------------------- 98 | When MAXCHAR is specified then use instead of the actual max length. 99 | Set TRUNC flag to indicate that values have been truncated. 100 | -----------------------------------------------------------------------; 101 | %let trunc=0; 102 | %if (&maxchar and (&maxchar < &maxlen)) %then %do; 103 | %let trunc=1; 104 | %let maxlen=&maxchar; 105 | %end; 106 | 107 | *----------------------------------------------------------------------; 108 | * Get values for all variables into tall skinny table. ; 109 | *----------------------------------------------------------------------; 110 | data &outval(rename=(__name=name __cvalue=cvalue __value=value)); 111 | set &data(keep=&varlist); 112 | keep __name __cvalue __value; 113 | %if (&sample) %then %do; 114 | if (mod(_n_,&sample) = 0); 115 | %end; 116 | %if (&anychar) %then %do; 117 | array _c _character_; 118 | %end; 119 | %if (&anynum) %then %do; 120 | array _n _numeric_; 121 | %end; 122 | length __name $32 __value 8 __cvalue $&maxlen; 123 | format __value best12.; 124 | %if (&anychar) %then %do; 125 | __value=.; 126 | do _i = 1 to dim(_c); 127 | call vname(_c{_i},__name); 128 | __name = upcase(__name); 129 | __cvalue = _c{_i}; 130 | output; 131 | end; 132 | %end; 133 | %if (&anynum) %then %do; 134 | __cvalue = ''; 135 | do _i = 1 to dim(_n); 136 | call vname(_n{_i},__name); 137 | __name = upcase(__name); 138 | __value = _n{_i}; 139 | output; 140 | end; 141 | %end; 142 | run; 143 | 144 | *----------------------------------------------------------------------; 145 | * Summarize to unique values and frequency. ; 146 | *----------------------------------------------------------------------; 147 | proc summary data=&outval nway missing; 148 | class name value cvalue / groupinternal; 149 | output out=&outval(keep=name value cvalue _freq_); 150 | run; 151 | 152 | %if (^&trunc) %then %do; 153 | *----------------------------------------------------------------------; 154 | * Calculate maximum actual length of character variables ; 155 | *----------------------------------------------------------------------; 156 | proc sql noprint; 157 | create table _maxlen as 158 | select name,max(length(cvalue)) as maxlen 159 | from &outval 160 | where cvalue ne ' ' 161 | group by name 162 | ; 163 | quit; 164 | %end; 165 | 166 | *----------------------------------------------------------------------; 167 | * Count number of unique values for each name.; 168 | *----------------------------------------------------------------------; 169 | proc summary data=&outval nway; 170 | by name; 171 | var _freq_; 172 | output max=maxfreq 173 | out=&outsum(keep=name _freq_ maxfreq rename=(_freq_=nval)) 174 | ; 175 | run; 176 | 177 | *----------------------------------------------------------------------; 178 | * Combine the attribute information with number of distinct values and ; 179 | * keep the actual first value. ; 180 | *----------------------------------------------------------------------; 181 | data &outsum; 182 | merge 183 | dsinfo 184 | &outsum 185 | &outval(drop=_freq_) 186 | %if (^&trunc) %then _maxlen; 187 | ; 188 | by name; 189 | if first.name; 190 | if nval=1 and ((type='char' and cvalue=' ') or 191 | (type='num' and value=.)) then nval=0; 192 | label value='First value (numeric)' 193 | cvalue='First value (character)' 194 | nval ='Number of distinct values (0=all missing)' 195 | maxfreq='Frequency of most frequent value' 196 | maxlen='Maximum length of character data' 197 | ; 198 | %if (&trunc) %then %do; 199 | maxlen=.; 200 | %end; 201 | 202 | run; 203 | 204 | 205 | %if (%length(&fname)) %then %do; 206 | /*---------------------------------------------------------------------- 207 | When FNAME is supplied then write summary to output text file. 208 | For names with more than &NVAL unique values, only list &nval values 209 | (half each from the start and the end) are listed. 210 | When PRINTN is requested then frequency count for each value will be 211 | printed before the values. 212 | When formatted value is different than the raw value then the formatted 213 | value is printed after the raw value. 214 | ----------------------------------------------------------------------*/ 215 | 216 | %*---------------------------------------------------------------------- 217 | Round NVAL up to next even number 218 | -----------------------------------------------------------------------; 219 | %if %sysfunc(mod(&nval,2)) %then %do; 220 | %let nval=%eval(&nval+1); 221 | %put NOTE: Rounding NVAL up to even number &nval..; 222 | %end; 223 | 224 | %if (&printn) %then %do; 225 | *----------------------------------------------------------------------; 226 | * Determine characters needed to display frequencies using COMMA format; 227 | *----------------------------------------------------------------------; 228 | proc sql noprint; 229 | select max(maxfreq) into :flen from &outsum; 230 | quit; 231 | %let flen=%sysfunc(max(&flen,&flen),comma32.); 232 | %let flen=&flen; 233 | %let flen=%length(&flen); 234 | %end; 235 | 236 | data _null_; 237 | merge &outsum &outval; 238 | by name; 239 | file &fname; 240 | if (_n_=1) then put 241 | '~' 69*'=' 242 | / memname "NOBS=&nobs [Display limited to &nval values]" 243 | %if (&trunc) %then " " ; 244 | / 70*'=' 245 | ; 246 | if (first.name) then do; 247 | k = 1; 248 | %if (&printn) %then %do; 249 | put @&flen 'N ' @; 250 | %end; 251 | put name 'LEN=' @; 252 | if type='char' then put '$'@; 253 | put length nval= @; 254 | if maxlen ne . then put maxlen= @; 255 | if format ne ' ' then put format= @; 256 | if name ne label and label ne ' ' then put label= @; 257 | put ; 258 | if nval then put 70*'-'; 259 | end; 260 | else k + 1; 261 | if nval=0 then out=0; 262 | else if nval<= &nval+1 then out=1; 263 | else out = (k <= ceil(&nval/2)) | (nval - k < ceil(&nval/2)); 264 | if (out); 265 | %if (&printn) %then %do; 266 | put _freq_ comma&flen.. +1 @; 267 | %end; 268 | if (type = 'num') then do; 269 | if (format = '') then put value best12.; 270 | else do; 271 | cvalue = putn(value,format); 272 | if (input(cvalue,??best12.) ^= value) then put value best12. +1 cvalue; 273 | else put value best12.; 274 | end; 275 | end; 276 | else do; 277 | put cvalue @; 278 | if (compress(format,'$1234567890.') ^= '') then do; 279 | fcvalue = left(putc(cvalue,format)); 280 | if (fcvalue ^= cvalue) then put @&maxlen+1 fcvalue; 281 | else put; 282 | end; 283 | else put; 284 | end; 285 | if (nval > &nval+1) & (k = ceil(&nval/2)) then put 286 | %if (&printn) %then &flen*'.' '.' ; 287 | '............' 288 | ; 289 | if (last.name) then put 70*'_'; 290 | run; 291 | 292 | %end; 293 | %quit: 294 | %mend dbcon; 295 | -------------------------------------------------------------------------------- /dblibchk.sas: -------------------------------------------------------------------------------- 1 | %macro dblibchk 2 | /*---------------------------------------------------------------------- 3 | Return DBTYPE, DBHOST and DBNAME for an existing SAS/Access library 4 | ----------------------------------------------------------------------*/ 5 | (lib /* LIBREF or LIBREF.MEMNAME format */ 6 | ,mdbtype=dbtype /* Macro variable to return ENGINE name */ 7 | ,mdbhost=dbhost /* Macro variable to return HOST name */ 8 | ,mdbname=dbname /* Macro variable to return SCHEMA/DATABASE name */ 9 | ) / minoperator mindelimiter=' '; 10 | /*---------------------------------------------------------------------- 11 | Check SASHELP.VLIBNAM view to see if &LIB is defined using SAS/Access by 12 | checking the existence of records where SYSNAME field represents 13 | 'Schema/User' or 'Schema/Owner'. 14 | 15 | The values of the view variables engine, path and sysvalue are used to 16 | populate the macro variables DBTYPE, DBHOST and DBNAME values, 17 | respectively. 18 | 19 | This will work for TERADATA, ORACLE and ODBC libname engines. 20 | Not sure if it works for other external databases. 21 | 22 | If return macro variables do not exist they will be made GLOBAL. 23 | 24 | If libref does not exist or is not using SAS/ACCESS then return macro 25 | variables will be blanked out and SYSRC will be set to 1. 26 | 27 | User cannot use macro variable names that conflict with these macro 28 | variables that are used by the macro inself. 29 | 30 | Parameters: LIB MDBTYPE MDBHOST MDBNAME 31 | SASHELP.VLIBNAM variables: LIBNAME ENGINE PATH SYSVALUE SYSNAME 32 | Local macro variables: DID RC KEEP WHERE LOCALE SYSOWNER 33 | 34 | Example usage: 35 | 36 | %dblibchk(tdwork) 37 | %put TDWORK uses engine &dbtype on server &dbhost to database &dbname..; 38 | 39 | ----------------------------------------------------------------------*/ 40 | %local did rc keep where locale sysowner; 41 | 42 | %*---------------------------------------------------------------------- 43 | KEEP is a list of variables to be read from SASHELP.VLIBNAM. List will 44 | be used in KEEP= dataset option and defined as local macro variables 45 | to be automatically filled by FETCH() function call. 46 | -----------------------------------------------------------------------; 47 | %let keep=libname engine path sysvalue sysname ; 48 | %local &keep ; 49 | 50 | %*---------------------------------------------------------------------- 51 | Make sure macro variables exist so that values can be returned. 52 | -----------------------------------------------------------------------; 53 | %if not %symexist(&mdbtype) %then %global &mdbtype ; 54 | %if not %symexist(&mdbhost) %then %global &mdbhost ; 55 | %if not %symexist(&mdbname) %then %global &mdbname ; 56 | 57 | %*---------------------------------------------------------------------- 58 | Clear values and set SYSRC=1 in case no libref is found. 59 | -----------------------------------------------------------------------; 60 | %let &mdbtype=; 61 | %let &mdbhost=; 62 | %let &mdbname=; 63 | %let sysrc=1; 64 | 65 | %*---------------------------------------------------------------------- 66 | A SAS LIBREF can only be 8 characters long. 67 | -----------------------------------------------------------------------; 68 | %let libname=%qupcase(%scan(&lib,1,.)); 69 | %if 1 <= %length(&libname) <= 8 %then %do; 70 | 71 | %*---------------------------------------------------------------------- 72 | When using UTF-8 some LOCALE settings use different text for SYSNAME. 73 | The ODBC engine uses Schema/Owner instead of Schema/User in metadata. 74 | -----------------------------------------------------------------------; 75 | %let sysname='Schema/User'; 76 | %let sysowner='Schema/Owner'; 77 | %if "%sysfunc(getoption(encoding))"="UTF-8" %then %do; 78 | %let locale=%sysfunc(getoption(locale)); 79 | %if &locale=JA_JP %then %do; 80 | %let sysname="E382B9E382ADE383BCE3839E2FE383A6E383BCE382B6E383BC"x; 81 | %let sysowner="E382B9E382ADE383BCE3839E2FE68980E69C89E88085"x; 82 | %end; 83 | %else %if (&locale in ZH_CN ZH_XX ZH_SG) %then %do; 84 | %let sysname="E6A8A1E5BC8F2FE794A8E688B7"x ; 85 | %let sysowner="E6A8A1E5BC8F2FE68980E69C89E88085"x; 86 | %end; 87 | %else %if (&locale in ZH_HK ZH_MO ZH_TW) %then %do; 88 | %let sysname="E7B590E6A78BE68F8FE8BFB02FE4BDBFE794A8E88085"x; 89 | %let sysowner="E7B590E6A78BE68F8FE8BFB02FE68980E69C89E88085"x; 90 | %end; 91 | %end; 92 | %*---------------------------------------------------------------------- 93 | Open SASHELP.VLIBNAM with KEEP and WHERE dataset options. Use CALL SET() 94 | to synch variable names to macro variables and fetch first matching 95 | observation. 96 | -----------------------------------------------------------------------; 97 | %let where=sysname in (&sysname,&sysowner) and libname="&libname"; 98 | %let did=%sysfunc(open(sashelp.vlibnam(keep=&keep where=(&where)))); 99 | %syscall set(did); 100 | %if not %sysfunc(fetch(&did)) %then %do; 101 | %*---------------------------------------------------------------------- 102 | When record is found then set the return macro variables. 103 | Note this assignment will also trim the trailing blanks that FETCH() 104 | put into the local macro variables. 105 | 106 | Set SYSRC=0 to indicate success. 107 | -----------------------------------------------------------------------; 108 | %let &mdbtype=&engine; 109 | %let &mdbhost=&path ; 110 | %let &mdbname=&sysvalue; 111 | %let sysrc=0; 112 | %end; 113 | 114 | %*---------------------------------------------------------------------- 115 | Close SYSHELP.VLIBNAM. 116 | -----------------------------------------------------------------------; 117 | %let rc=%sysfunc(close(&did)) ; 118 | %end; 119 | %mend dblibchk ; 120 | -------------------------------------------------------------------------------- /direxist.sas: -------------------------------------------------------------------------------- 1 | %macro direxist 2 | /*---------------------------------------------------------------------- 3 | Test if directory exists 4 | ----------------------------------------------------------------------*/ 5 | (path /* Name of directory to test for existance */ 6 | ); 7 | /*---------------------------------------------------------------------- 8 | Test if directory exists. Returns value 1 or 0. 9 | 1 - Directory exists 10 | 0 - Directory does not exist 11 | 12 | Global macro variable SYSRC will be set to 1 when a file is found 13 | instead of a directory. 14 | 15 | ----------------------------------------------------------------------*/ 16 | %local return rc did fileref; 17 | %*---------------------------------------------------------------------- 18 | Set up return values as normal failure to find path. 19 | -----------------------------------------------------------------------; 20 | %let return=0; 21 | %let sysrc=0; 22 | 23 | %*---------------------------------------------------------------------- 24 | If path is not specified or does not exist then return normal failure. 25 | -----------------------------------------------------------------------; 26 | %if (%bquote(&path) = ) %then %goto quit; 27 | %if ^%sysfunc(fileexist(&path)) %then %goto quit; 28 | 29 | %*---------------------------------------------------------------------- 30 | Try to open it using DOPEN function. 31 | Return 1 if it can be opened. 32 | Otherwise set SYSRC=1 to mean that PATH exists but is not a directory. 33 | -----------------------------------------------------------------------; 34 | %if (0=%sysfunc(filename(fileref,&path))) %then %do; 35 | %let did=%sysfunc(dopen(&fileref)); 36 | %if (&did) %then %do; 37 | %let return=1; 38 | %let rc=%sysfunc(dclose(&did)); 39 | %end; 40 | %else %let sysrc=1; 41 | %let rc=%sysfunc(filename(fileref)); 42 | %end; 43 | 44 | %quit: 45 | %*---------------------------------------------------------------------- 46 | Return the value as the result of the macro. 47 | -----------------------------------------------------------------------; 48 | &return 49 | %mend; 50 | -------------------------------------------------------------------------------- /dirtree.sas: -------------------------------------------------------------------------------- 1 | %macro dirtree 2 | /*--------------------------------------------------------------------------- 3 | Build dataset of files in directory tree(s) 4 | ----------------------------------------------------------------------------*/ 5 | (directory /* Pipe delimited directory list (default=.) */ 6 | ,out=dirtree /* Output dataset name */ 7 | ,maxdepth=120 /* Maximum tree depth */ 8 | ); 9 | /*--------------------------------------------------------------------------- 10 | Use SAS functions to gather list of files and directories 11 | 12 | directory - Pipe delimited list of top level directories 13 | 14 | out - Dataset to create 15 | maxdepth - Maximum depth of subdirectories to query 16 | 17 | Output dataset structure 18 | --NAME-- Len Format Description 19 | FILENAME $256 Name of file in directory 20 | TYPE $1 File or Directory? (F/D) 21 | SIZE 8 COMMA20. Filesize in bytes 22 | DATE 4 YYMMDD10. Date file last modified 23 | TIME 4 TOD8. Time of day file last modified 24 | DEPTH 3 Tree depth 25 | PATH $256 Directory name 26 | 27 | Size is not available for the directories. 28 | LASTMOD timestamp is only available on Unix for directories. 29 | 30 | Will not scan the subtree of a directory with a path that is 31 | longer then 256 bytes. For such nodes TYPE will be set to L . 32 | 33 | ----------------------------------------------------------------------------*/ 34 | %local fileref ; 35 | %let fileref=__FL__ ; 36 | %if 0=%length(&directory) %then %let directory=. ; 37 | 38 | * Setup dataset and seed with starting directory list ; 39 | data &out; 40 | length filename $256 type $1 size 8 date time 4 depth 3 path $256 ; 41 | retain filename ' ' depth 0 type ' ' date . time . size . ; 42 | format size comma20. date yymmdd10. time tod8. ; 43 | do _n_=1 to countw(symget('directory'),'|'); 44 | path=scan(symget('directory'),_n_,'|'); 45 | output; 46 | end; 47 | run; 48 | 49 | %* Allow use of empty OUT= dataset parameter ; 50 | %let out=&syslast; 51 | 52 | data &out; 53 | modify &out; 54 | retain sep "%sysfunc(ifc(&sysscp=WIN,\,/))"; 55 | retain maxdepth &maxdepth; 56 | * Create FILEREF pointing to current file/directory ; 57 | rc1=filename("&fileref",catx('/',path,filename)); 58 | if rc1 then do; 59 | length message $256; 60 | message=sysmsg(); 61 | put 'ERROR: Unable to create fileref for ' path= filename= ; 62 | put 'ERROR- ' message ; 63 | stop; 64 | end; 65 | * Try to open as a directory to determine type ; 66 | did=dopen("&fileref"); 67 | type = ifc(did,'D','F'); 68 | if type='D' then do; 69 | * Make sure directory name is not too long to store. ; 70 | if length(catx('/',path,filename)) > vlength(path) then do; 71 | put 'NOTE: Directory name too long. ' path= filename= ; 72 | type='L'; 73 | rc3=dclose(did); 74 | end; 75 | else do; 76 | * Move filename into the PATH and if on Unix set lastmod ; 77 | path=catx(sep,path,filename); 78 | filename=' '; 79 | if sep='/' then do; 80 | lastmod = input(dinfo(did,doptname(did,5)),nldatm100.); 81 | date=datepart(lastmod); 82 | time=timepart(lastmod); 83 | end; 84 | end; 85 | end; 86 | else do; 87 | * For a file try to open file and get file information ; 88 | fid=fopen("&fileref",'i',0,'b'); 89 | if fid then do; 90 | lastmod = input(finfo(fid,foptname(fid, 5)), nldatm100.); 91 | date=datepart(lastmod); 92 | time=timepart(lastmod); 93 | size = input(finfo(fid,foptname(fid,ifn(sep='/',6,4))),32.); 94 | rc2 = fclose(fid); 95 | end; 96 | end; 97 | * Update the observation in the dataset ; 98 | replace; 99 | if type='D' then do; 100 | * When current file is a directory add directory members to dataset ; 101 | depth=depth+1; 102 | if depth > maxdepth then put 'NOTE: ' maxdepth= 'reached, not reading members of ' path= ; 103 | else do i=1 to dnum(did); 104 | filename=dread(did,i); 105 | output; 106 | end; 107 | rc3=dclose(did); 108 | end; 109 | * Clear the fileref ; 110 | rc4=filename("&fileref"); 111 | run; 112 | 113 | %mend dirtree; 114 | -------------------------------------------------------------------------------- /dquote.sas: -------------------------------------------------------------------------------- 1 | %macro dquote(value); 2 | %sysfunc(quote(%superq(value))) 3 | %mend dquote; 4 | -------------------------------------------------------------------------------- /ds2post.sas: -------------------------------------------------------------------------------- 1 | %macro ds2post 2 | /*---------------------------------------------------------------------------- 3 | Generate data step suitable for on-line posting to create an existing dataset 4 | ----------------------------------------------------------------------------*/ 5 | (data /* Name of input (def=&syslast Use data=-help to see syntax in log)*/ 6 | ,file /* Fileref or quoted physical name of where to write code (def=LOG) */ 7 | ,obs /* Number of observations to include. Use obs=max for all (def=20) */ 8 | ,target /* Dataset name to create. (def=memname of input dataset) */ 9 | ); 10 | /*---------------------------------------------------------------------------- 11 | You can use this macro to create a data step for sharing an example of your 12 | existing data. It will create a data step that will re-create the existing 13 | dataset variables formatting (including the order, type, length, format, 14 | informat and label) and data by reading from in-line delimited data lines. 15 | 16 | OBS=MAX will dump all of the data. Default is the first 20 observations. 17 | 18 | You can use the TARGET parameter to change the dataset name used in the 19 | generated code. By default it will create a work dataset with the same 20 | member name as the input dataset. 21 | 22 | For the FILE parameter you can use either a fileref or a quoted physical 23 | filename. Note that if you provide an unquoted value that is not a fileref 24 | then the macro will assume you meant to use that as the physical name of the 25 | file. The macro uses the fileref of _CODE_ for the temporary file it uses 26 | to generate the code into. So if you call it with FILE=_CODE_ it will leave 27 | the generated program in that temporary file. 28 | 29 | The data step will have INPUT, LENGTH, FORMAT, INFORMAT and LABEL statements 30 | (when needed) in that order. 31 | 32 | To insure that data transfers correctly in spite of the potential of variables 33 | with mismatched FORMAT and INFORMAT in the source dataset the values will be 34 | written using raw data format. In the generated INPUT statement all 35 | character variables will use $ informat (setting their length) and any numeric 36 | format that uses an informat other the default informat will include :F. 37 | informat in the INPUT statement. 38 | 39 | A LENGTH statement will only be generated for numeric variables with length 40 | less than 8. Numeric variables of length 2 (valid only on IBM Mainframes) will 41 | be set to length 3 instead. The length of characters variables will be set by 42 | the informat used in the INPUT statement. 43 | 44 | There are some limits on its ability to replicate exactly the data you have, 45 | mainly due to the use of delimited data. 46 | 47 | - Leading spaces on character variables are not preserved. 48 | - Embedded CR or LF in character variables will cause problems. 49 | - There could be slight (E-14) rounding of floating point numbers 50 | 51 | Also in-line data is not that suitable for really long data lines. In that 52 | case you could get better results by copying the data lines to a separate 53 | file and modifing the data step to read from that file instead of in-line 54 | data. 55 | ------------------------------------------------------------------------------ 56 | Examples: 57 | * Pull macro definition from GITHUB and dump code to the SAS log ; 58 | filename ds2post url 59 | 'https://raw.githubusercontent.com/sasutils/macros/master/ds2post.sas' 60 | ; 61 | %include ds2post ; 62 | %ds2post(sashelp.class) 63 | 64 | * Dump code to the results window ; 65 | %ds2post(sashelp.class,file=print) 66 | 67 | * Dump complete dataset to an external file ; 68 | %ds2post(mydata,obs=max,file="mydata.sas") 69 | 70 | ----------------------------------------------------------------------------*/ 71 | %local _error ll libname memname memlabel; 72 | %*--------------------------------------------------------------------------- 73 | Set maximum line length to use for wrapping the generated SAS statements. 74 | ----------------------------------------------------------------------------; 75 | %let ll=72 ; 76 | 77 | %*--------------------------------------------------------------------------- 78 | Check user parameters. 79 | ----------------------------------------------------------------------------; 80 | %let _error=0; 81 | %if "%upcase(%qsubstr(&data.xx,1,2))" = "-H" %then %let _error=1; 82 | %else %do; 83 | %if not %length(&data) %then %let data=&syslast; 84 | %if not (%sysfunc(exist(&data)) or %sysfunc(exist(&data,view))) %then %do; 85 | %let _error = 1; 86 | %put ERROR: "&data" is not a valid value for the DATA parameter.; 87 | %put ERROR: Unable to find the dataset. ; 88 | %end; 89 | %else %do; 90 | %let memname=%upcase(%scan(&data,-1,.)); 91 | %let libname=%upcase(%scan(work.&data,-2,.)); 92 | %end; 93 | %end; 94 | %if not %length(&file) %then %let file=log; 95 | %else %if %sysfunc(indexc(&file,%str(%'%"))) or %length(&file)>8 %then 96 | %let file=%sysfunc(quote(%qsysfunc(dequote(&file)),%str(%'))); 97 | %else %if %sysfunc(indexw(LOG _CODE_ PRINT,%upcase(&file),%str( ))) %then ; 98 | %else %if %sysfunc(fileref(&file))<=0 %then ; 99 | %else %let file=%sysfunc(quote(&file,%str(%'))); 100 | %if not %length(&obs) %then %let obs=20; 101 | %else %let obs=%upcase(&obs); 102 | %if "&obs" ne "MAX" %then %if %sysfunc(verify(&obs,0123456789)) %then %do; 103 | %let _error = 1; 104 | %put ERROR: "&obs" is not a valid value for the OBS parameter.; 105 | %put ERROR: Valid values are MAX or non-negative integer. ; 106 | %end; 107 | %if not %length(&target) %then %let target=work.%qscan(&data,-1,.); 108 | 109 | %if (&_error) %then %do; 110 | *----------------------------------------------------------------------------; 111 | * When there are parameter issues then write instructions to the log. ; 112 | *----------------------------------------------------------------------------; 113 | data _null_; 114 | put 115 | '%DS2POST' ' - SAS macro to copy data into a SAS Data Step in a ' 116 | 'form which you can post to on-line forums.' 117 | //'Syntax:' ' %ds2post(data,file,obs,target)' 118 | //' data = Name of SAS dataset (or view) that you want to output.' 119 | ' Default is last created dataset.' ' Use data=-help to print instructions.' 120 | //' file = Fileref or quoted physical filename for code.' 121 | ' Default of file=log will print code to the SAS log.' 122 | ' file=print will print code to results.' 123 | //' obs = Number of observations to output. Default obs=20.' 124 | ' Use obs=MAX to copy complete dataset.' 125 | //' target = Name to use for generated dataset.' 126 | ' Default is to make work dataset using the name of the input.' 127 | //'For more information see source code available on github at ' 128 | /'https://raw.githubusercontent.com/sasutils/macros/master/ds2post.sas' 129 | ; 130 | run; 131 | %end; 132 | %else %do; 133 | *----------------------------------------------------------------------------; 134 | * Get member label in format of dataset option. ; 135 | * Get dataset contents information in a format to facilitate code generation.; 136 | * Column names reflect data statement that uses the value. ; 137 | *----------------------------------------------------------------------------; 138 | proc sql noprint; 139 | select cats('(label=',quote(trim(memlabel),"'"),')') 140 | into :memlabel trimmed 141 | from dictionary.tables 142 | where libname="&libname" and memname="&memname" and not missing(memlabel) 143 | ; 144 | create table _ds2post_ as 145 | select varnum 146 | , nliteral(name) as name length=66 147 | , substrn(informat,1,findc(informat,' .',-49,'kd')) as inf length=32 148 | , case when type='char' then cats(':$',length,'.') 149 | when not (lowcase(calculated inf) in ('best','f',' ') 150 | and scan(informat,2,'.') = ' ') then ':F.' 151 | else ' ' end as input length=8 152 | , case when type='num' and length < 8 then cats(max(3,length)) 153 | else ' ' end as length length=1 154 | , lowcase(format) as format length=49 155 | , lowcase(informat) as informat length=49 156 | , case when missing(label) then ' ' else quote(trim(label),"'") 157 | end as label length=300 158 | from dictionary.columns 159 | where libname="&libname" and memname="&memname" 160 | order by varnum 161 | ; 162 | quit; 163 | *----------------------------------------------------------------------------; 164 | * Generate data step code ; 165 | * - For each statement use value of variable named the same as the statement.; 166 | * - Only variables that are required in that statement are generated. ; 167 | * - For LABEL statement use = between name and value instead of space. ; 168 | * - Wrap statements when lines get too long. ; 169 | * - Eliminate statements when no variables required that statement. ; 170 | *----------------------------------------------------------------------------; 171 | filename _code_ temp; 172 | data _null_; 173 | file _code_ column=cc ; 174 | set _ds2post_ (obs=1) ; 175 | put "data &target &memlabel;" ; 176 | put @3 "infile datalines dsd dlm='|' truncover;" ; 177 | length statement $10 string $370 ; 178 | do statement='input','length','format','informat','label'; 179 | call missing(any,anysplit); 180 | put @3 statement @ ; 181 | do p=1 to nobs ; 182 | set _ds2post_ point=p nobs=nobs ; 183 | string=vvaluex(statement); 184 | if statement='input' or not missing(string) then do; 185 | any=1; 186 | string=catx(ifc(statement='label','=',' '),name,string); 187 | if &ll<(cc+length(string)) then do; 188 | anysplit=1; 189 | put / @5 @ ; 190 | end; 191 | put string @ ; 192 | end; 193 | end; 194 | if anysplit then put / @3 @ ; 195 | if not any then put @1 10*' ' @1 @ ; 196 | else put ';' ; 197 | end; 198 | put 'datalines4;' ; 199 | run; 200 | *----------------------------------------------------------------------------; 201 | * Generate data lines ; 202 | *----------------------------------------------------------------------------; 203 | data _null_; 204 | file _code_ mod dsd dlm='|'; 205 | %if (&obs ne MAX) %then %do; 206 | if _n_ > &obs then stop; 207 | %end; 208 | set &data ; 209 | format _numeric_ best32. _character_ ; 210 | put (_all_) (+0) ; 211 | run; 212 | data _null_; 213 | file _code_ mod ; 214 | put ';;;;'; 215 | run; 216 | %if "%qupcase(&file)" ne "_CODE_" %then %do; 217 | *----------------------------------------------------------------------------; 218 | * Copy generated code to target file name and remove temporary file. ; 219 | *----------------------------------------------------------------------------; 220 | data _null_ ; 221 | infile _code_; 222 | file &file ; 223 | input; 224 | put _infile_; 225 | run; 226 | filename _code_ ; 227 | %end; 228 | *----------------------------------------------------------------------------; 229 | * Remove generated metadata. ; 230 | *----------------------------------------------------------------------------; 231 | proc delete data=_ds2post_; 232 | run; 233 | %end; 234 | %mend ds2post ; 235 | -------------------------------------------------------------------------------- /dslist.sas: -------------------------------------------------------------------------------- 1 | %macro dslist 2 | /*---------------------------------------------------------------------- 3 | Generate dataset list to summarize SAS datasets in libraries 4 | ----------------------------------------------------------------------*/ 5 | (librefs=WORK /* Space delimited list of LIBREFS to include */ 6 | ,out=dslist /* Output dataset. Default is WORK.dslist */ 7 | ,exclude=dslist /* Space delimited list of members to exclude */ 8 | ,select= /* Space delimited list of members to include */ 9 | ,usubjid= /* Variables that uniquely identify a subject */ 10 | ,countobs=0 /* Count NOBS when missing? (0/1) */ 11 | ); 12 | /*---------------------------------------------------------------------- 13 | Generate dataset list with one record per dataset. Information derived 14 | will include the number of variables and observations and optionally the 15 | number of subjects. 16 | 17 | The resulting dataset will have the following variables: 18 | 19 | # Variable Type Len Format Informat Label 20 | 1 LIBNAME Char 8 Library reference 21 | 2 MEMNAME Char 32 Dataset name 22 | 3 MEMTYPE Char 8 Dataset type 23 | 4 MODATE Num 8 DATETIME. DATETIME. Last mod datetime 24 | 5 NOBS Num 8 Number of observations 25 | 6 NSUBJECT Num 8 Number of subjects 26 | 7 NVAR Num 8 Number of variables 27 | 8 OBSLEN Num 8 Observation length 28 | 9 MEMLABEL Char 256 Dataset label 29 | 10 PATH Char 1024 Directory of file 30 | 31 | ----------------------------------------------------------------------- 32 | $Calls to: parmv.sas qlist.sas 33 | ----------------------------------------------------------------------- 34 | $Usage notes: 35 | If USUBJID is supplied then number of subjects are counted and stored 36 | into NSUBJECT variable in the output dataset for the datasets that have 37 | the USUBJID variables. Otherwise the NSUBJECT variable will have missing 38 | values. 39 | 40 | Set COUNTOBS to generate an SQL query to count the number of 41 | observations for each members where the value is not available in SAS 42 | metadata DICTIONARY.TABLES. This is usefull for views or when using 43 | SAS/Access to external databases such as ORACLE or TERADATA. 44 | 45 | Using the USUBJID or COUNTOBS options might take a long time for large 46 | datasets. 47 | 48 | The SELECT and EXCLUDE lists apply to only membername part of dataset name. 49 | 50 | Examples: 51 | * Get dslist for RAW and VA libraries, caclulate number of subjects ; 52 | %dslist(librefs=raw va,usubjid=pid) 53 | 54 | ----------------------------------------------------------------------- 55 | Modification History 56 | ----------------------------------------------------------------------- 57 | 2007/10/08 abernt Initial revision 58 | 2009/03/11 abernt Eliminate querying variable names from unused datasets 59 | 2011/02/28 abernt Remove view with same name as OUT dataset 60 | 2012/12/02 abernt Added option to count NOBS 61 | 2024/07/05 sasutils Post to Github. 62 | ----------------------------------------------------------------------*/ 63 | %local macro parmerr; 64 | %local dslist nds nds2 i nsubjid csubjid nsubject nobs ; 65 | 66 | %let macro=DSLIST; 67 | %let parmerr=0; 68 | 69 | %parmv(librefs,_req=1,_words=1) 70 | %parmv(out,_def=dslist) 71 | %parmv(usubjid,_words=1) 72 | %parmv(exclude,_words=1) 73 | %parmv(select,_words=1) 74 | %if (&parmerr) %then %goto quit; 75 | 76 | *----------------------------------------------------------------------; 77 | * Create &out from dictionary.tables and dictionary.members ; 78 | *----------------------------------------------------------------------; 79 | proc sql noprint; 80 | %if %sysfunc(exist(&out,view)) %then %do; 81 | drop view &out; 82 | %end; 83 | create table &out as 84 | select 85 | t.LIBNAME as LIBNAME label='Library reference' 86 | ,upcase(t.memname) as MEMNAME label='Dataset name' 87 | ,t.MEMTYPE as MEMTYPE label='Dataset type' 88 | ,t.MODATE as MODATE label='Last mod datetime' 89 | ,t.nobs-t.delobs as NOBS label='Number of observations' 90 | ,. as NSUBJECT label='Number of subjects' 91 | ,t.NVAR as NVAR label='Number of variables' 92 | ,t.OBSLEN as OBSLEN label='Observation length' 93 | ,t.memlabel as MEMLABEL label='Dataset label' 94 | ,v.PATH as PATH label='Directory of file' 95 | from dictionary.tables t left join 96 | (select distinct libname,path from dictionary.members m 97 | where m.libname in %qlist(&librefs) 98 | ) v 99 | on t.libname = v.libname 100 | where t.libname in %qlist(&librefs) 101 | %if %length(&select) %then 102 | and t.memname in %qlist(&select) 103 | ; 104 | %if %length(&exclude) %then 105 | and t.memname ^in %qlist(&exclude) 106 | ; 107 | order by 1,2 108 | ; 109 | %let nds=&sqlobs; 110 | quit; 111 | 112 | %if (%length(&usubjid) and &nds) %then %do; 113 | %*---------------------------------------------------------------------- 114 | Count number of variables and generate version with commas for SQL. 115 | -----------------------------------------------------------------------; 116 | %let usubjid=%sysfunc(compbl(&usubjid)); 117 | %let nsubjid=%sysfunc(countw(&usubjid,%str( ))); 118 | %let csubjid=%sysfunc(tranwrd(&usubjid,%str( ),%str(,))); 119 | 120 | *----------------------------------------------------------------------; 121 | * Count number of subjects when dataset has all USUBJID variables. ; 122 | *----------------------------------------------------------------------; 123 | proc sql noprint; 124 | select distinct trim(d.libname)||'.'||trim(d.memname) 125 | into :dslist separated by '/' 126 | from dictionary.columns c 127 | , &out d 128 | where c.libname in %qlist(&librefs) 129 | and c.libname = d.libname 130 | and c.memname = d.memname 131 | and upcase(c.name) in %qlist(&usubjid) 132 | group by d.libname,d.memname 133 | having count(*)=&nsubjid 134 | ; 135 | %let nds2=&sqlobs; 136 | %do i=1 %to &nds2; 137 | %let nsubject=0; 138 | select count(*) into :nsubject 139 | from (select distinct &csubjid from %scan(&dslist,&i,/)) ; 140 | update &out set nsubject = &nsubject 141 | where libname="%scan(%scan(&dslist,&i,/),1,.)" 142 | and memname="%scan(%scan(&dslist,&i,/),2,.)" 143 | ; 144 | %end; 145 | quit; 146 | %end; 147 | 148 | %if (&countobs and &nds) %then %do; 149 | *----------------------------------------------------------------------; 150 | * Count number of observations when NOBS is missing ; 151 | *----------------------------------------------------------------------; 152 | proc sql noprint; 153 | select distinct trim(d.libname)||'.'||trim(d.memname) 154 | into :dslist separated by '/' 155 | from &out d 156 | where d.nobs is missing 157 | ; 158 | %let nds2=&sqlobs; 159 | %do i=1 %to &nds2; 160 | %let nobs=0; 161 | select count(*) into :nobs from %scan(&dslist,&i,/) ; 162 | update &out set nobs = &nobs 163 | where libname="%scan(%scan(&dslist,&i,/),1,.)" 164 | and memname="%scan(%scan(&dslist,&i,/),2,.)" 165 | ; 166 | %end; 167 | quit; 168 | %end; 169 | 170 | %quit: 171 | %mend dslist; 172 | -------------------------------------------------------------------------------- /fileref.sas: -------------------------------------------------------------------------------- 1 | %macro fileref 2 | /*---------------------------------------------------------------------------- 3 | Verify whether a fileref has been assigned 4 | ----------------------------------------------------------------------------*/ 5 | (fileref /* Fileref to test */ 6 | ); 7 | /*---------------------------------------------------------------------------- 8 | A value of zero indicates that the fileref and external file both exist. 9 | 10 | A negative return code indicates that the fileref exists but the physical file 11 | associated with the fileref does not exist. 12 | 13 | A positive value indicates that the fileref is not assigned. 14 | 15 | Note that a fileref must be valid SAS name of length 1 to 8. The macro will 16 | return 1 when an invalid fileref parameter is supplied. 17 | ----------------------------------------------------------------------------*/ 18 | 19 | %*---------------------------------------------------------------------------- 20 | FILEREF empty or blank value. 21 | -----------------------------------------------------------------------------; 22 | %if %bquote(&fileref)= %then 1; 23 | 24 | %*---------------------------------------------------------------------------- 25 | FILEREF too long. 26 | -----------------------------------------------------------------------------; 27 | %else %if %length(&fileref) > 8 %then 1; 28 | 29 | %*---------------------------------------------------------------------------- 30 | FILEREF not a valid SAS name. 31 | -----------------------------------------------------------------------------; 32 | %else %if 0 = %sysfunc(nvalid(&fileref,v7)) %then 1; 33 | 34 | %*---------------------------------------------------------------------------- 35 | Return result of the FILEREF() function. 36 | -----------------------------------------------------------------------------; 37 | %else %sysfunc(fileref(&fileref)) ; 38 | 39 | %mend fileref; 40 | -------------------------------------------------------------------------------- /fread.sas: -------------------------------------------------------------------------------- 1 | %macro fread 2 | /*---------------------------------------------------------------------- 3 | Read file using only macro code. 4 | ----------------------------------------------------------------------*/ 5 | (file /* Fileref or path name */ 6 | ,mode=1 /* Operation Mode */ 7 | /* Mode=1 returns macro variable array (requires %UNQUOTE()) */ 8 | /* Mode=2 puts file contents to SAS log */ 9 | /* Mode=3 returns file contents as macro result */ 10 | ,lineno=0 /* Include line numbers when Mode=2? 0=No 1=Yes */ 11 | ,eol=| /* Characters to output after each line when MODE=3 */ 12 | ); 13 | 14 | /*---------------------------------------------------------------------- 15 | Read a file to (mode=1) local macro variables, (mode=2) the log or 16 | (mode=3) the macro function result. Values will be macro quoted. 17 | 18 | MODE=1 19 | ------ 20 | This will return the lines in the form of a series of %LET statements 21 | to create an array of macro variables (N,W1,W2,....). You will need 22 | to wrap the %FREAD() call inside of %UNQUOTE() to have the macro 23 | variables created in the calling environment. 24 | 25 | %unquote(%fred(config.dat)) 26 | %do i=1 %to &n ; 27 | %if (&&w&i = YES) %then %let found=1; 28 | %end; 29 | 30 | MODE=2 31 | ------ 32 | Write the file to the SAS log using %PUT statements. Set LINENO=1 to 33 | have the lines prefixed with 5 digit line numbers. 34 | 35 | MODE=3 36 | ------ 37 | Return the file as the result of the macro call. You can use the EOL= 38 | parameter to insert a delimiter string between the lines. 39 | 40 | Note files larger than macro variable limit (64K bytes) will not work 41 | with MODE=1 or MODE=3. 42 | 43 | Note: This macro requires %FILEREF() macro. 44 | 45 | ----------------------------------------------------------------------- 46 | This macro was adopted from code developed by Tom Hoffman. 47 | 48 | History: 49 | 50 | 07APR00 TRHoffman Creation 51 | 01MAY00 TRHoffman Cleared fileref. Protected length statement against 52 | unmatched parentheses in input file. 53 | 03MAY00 TRHoffman Corrected invalid file close. 54 | 08MAY00 TRHoffman Added MODE parameter. 55 | 2016-08-12 abernt Removed the code that was stripping macro triggers. 56 | Replaced with QUOTE()/DEQUOTE() calls. 57 | Added LINENO and EOL parameters. 58 | ----------------------------------------------------------------------*/ 59 | %local n filerc fileref fid text rc j sep ; 60 | 61 | %*---------------------------------------------------------------------- 62 | Initialize line counter. 63 | -----------------------------------------------------------------------; 64 | %let n = 0; 65 | 66 | %if %fileref(&file)<=0 %then %do; 67 | %*---------------------------------------------------------------------- 68 | When FILE is an existing FILEREF then use it. 69 | -----------------------------------------------------------------------; 70 | %let filerc = 1; 71 | %let fileref=&file; 72 | %end; 73 | %else %if %sysfunc(fileexist(&file)) %then %do; 74 | %*---------------------------------------------------------------------- 75 | Create new fileref for the existing file. 76 | -----------------------------------------------------------------------; 77 | %let filerc = %sysfunc(filename(fileref,&file)); 78 | %end; 79 | 80 | %if %length(&fileref) %then %do; 81 | %*---------------------------------------------------------------------- 82 | Open file for streaming input access. 83 | -----------------------------------------------------------------------; 84 | %let fid = %sysfunc(fopen(&fileref,s)); 85 | 86 | %if (&fid > 0) %then %do; 87 | 88 | %*---------------------------------------------------------------------- 89 | Write a blank line before the output when MODE=2. 90 | -----------------------------------------------------------------------; 91 | %if (&mode=2) %then %put %str( ); 92 | 93 | %*---------------------------------------------------------------------- 94 | Read through file and process each line. 95 | -----------------------------------------------------------------------; 96 | %do %while(%sysfunc(fread(&fid)) = 0); 97 | %let n = %eval(&n + 1); 98 | %let rc = %sysfunc(fget(&fid,text,32767)); 99 | 100 | %if (&mode = 1) %then %do; 101 | %*---------------------------------------------------------------------- 102 | MODE=1 Store the quoted value into local macro variable. 103 | -----------------------------------------------------------------------; 104 | %local w&n; 105 | %let w&n = %sysfunc(quote(%superq(text),%str(%'))); 106 | %end; 107 | 108 | %else %if (&mode = 2) %then %do; 109 | %*---------------------------------------------------------------------- 110 | MODE=2 Write line to LOG with optional line numbers. 111 | -----------------------------------------------------------------------; 112 | %if ^(&lineno) %then %put %superq(text) ; 113 | %else %put %sysfunc(putn(&n,Z5)) %superq(text) ; 114 | %end; 115 | 116 | %else %do; 117 | %*---------------------------------------------------------------------- 118 | MODE=3 Return the line with optional end of line string. 119 | -----------------------------------------------------------------------; 120 | %*;&sep.%superq(text) 121 | %let sep=%superq(eol); 122 | %end; 123 | 124 | %end; 125 | 126 | %*---------------------------------------------------------------------- 127 | Write a blank line after the output when MODE=2. 128 | -----------------------------------------------------------------------; 129 | %if (&mode=2) %then %put %str( ); 130 | 131 | %let rc = %sysfunc(fclose(&fid)); 132 | %end; 133 | 134 | %*---------------------------------------------------------------------- 135 | Clear fileref when assigned by macro, 136 | -----------------------------------------------------------------------; 137 | %if ^(&filerc) %then %let rc = %sysfunc(filename(fileref)); 138 | 139 | %end; 140 | 141 | %if (&mode = 1) %then %do; 142 | %*---------------------------------------------------------------------- 143 | Create quoted %let statements to recreate the macro variables. 144 | 145 | Use %QSYSFUNC(DEQUOTE()) to remove the quoting added above. 146 | -----------------------------------------------------------------------; 147 | %*;%nrstr(%let )n=&n%str(;) 148 | %do j = 1 %to &n; 149 | %*;%nrstr(%let )w&j=%nrstr(%qsysfunc)(dequote(&&w&j))%str(;)%*; 150 | %end; 151 | %end; 152 | 153 | %mend fread; 154 | -------------------------------------------------------------------------------- /github_include.sas: -------------------------------------------------------------------------------- 1 | %macro github_include 2 | /*----------------------------------------------------------------------------- 3 | Run SAS code from github repository 4 | -----------------------------------------------------------------------------*/ 5 | (namelist /* Space delimited list of filenames (without the .sas extension) */ 6 | ,gituser=sasutils /* GITHUB username */ 7 | ,repository=macros /* GITHUB repository */ 8 | ,branch=master /* GITHUB repository branch */ 9 | ,source2= /* Override default setting of SOURCE2 option */ 10 | ); 11 | %local i url; 12 | 13 | %* Adjust value of SOURCE2 parameter to valid syntax for %INCLUDE statement ; 14 | %if %length(&source2) %then %do; 15 | %if %sysfunc(findw(n no 0 nosource nosource2,&source2,/,sit)) %then %do; 16 | %let source2=/nosource2; 17 | %end; 18 | %else %if %sysfunc(findw(y yes 1 source source2,&source2,/,sit)) %then %do; 19 | %let source2=/source2; 20 | %end; 21 | %else %do; 22 | %put WARNING: Value &=source2 not recognized. Will be ignored. ; 23 | %let source2=; 24 | %end; 25 | %end; 26 | 27 | %do i=1 %to %sysfunc(countw(&namelist,%str( ))); 28 | %let url=https://raw.githubusercontent.com/&gituser/&repository/&branch; 29 | %let url=%sysfunc(quote(&url/%qscan(&namelist,&i,%str( )).sas,%str(%'))); 30 | filename _github_ url &url; 31 | %include _github_ &source2; 32 | %end; 33 | filename _github_ ; 34 | %mend github_include; 35 | -------------------------------------------------------------------------------- /lowcase.sas: -------------------------------------------------------------------------------- 1 | %macro lowcase/parmbuff; 2 | /*---------------------------------------------------------------------- 3 | Replacement for SAS supplied LOWCASE macro that eliminates errors 4 | 5 | SYSPBUFF must have at least 3 characters or nothing was passed in. 6 | Use SYSPBUFF to generate %QUOTE function call so value can be passed 7 | to %SYSFUNC(LOWCASE()). 8 | ----------------------------------------------------------------------*/ 9 | %if %length(&syspbuff)>2 %then %sysfunc(lowcase(%quote&syspbuff)); 10 | %mend; 11 | -------------------------------------------------------------------------------- /macdelete.sas: -------------------------------------------------------------------------------- 1 | %macro macdelete(delete,keep); 2 | /*---------------------------------------------------------------------------- 3 | Remove compiled macros using %SYSMACDELETE macro statement. 4 | 5 | Use DELETE parameter to list macro names to delete. 6 | Use KEEP parameter to list macro names to NOT delete. 7 | Calling it with no values will delete all macros not currently running. 8 | ----------------------------------------------------------------------------*/ 9 | %local libname memname objname objtype fid i; 10 | %do i=1 %to %sysmexecdepth; 11 | %let keep=%sysmexecname(&i) &keep; 12 | %end; 13 | %if %length(&delete) %then %let delete=and findw("&delete",objname,',','sit'); 14 | %let fid=%sysfunc(open( sashelp.vcatalg(keep=libname memname objname objtype 15 | where=(libname='WORK' and objtype='MACRO' and memname like 'SASMAC_' 16 | and not findw("&keep",objname,',','sit') &delete)))); 17 | %if (&fid) %then %do; 18 | %syscall set(fid); 19 | %do %while(0=%sysfunc(fetch(&fid))); 20 | %put %sysfunc(compbl(Removing &objname from &libname catalog &memname)); 21 | %sysmacdelete &objname; 22 | %end; 23 | %let fid=%sysfunc(close(&fid)); 24 | %end; 25 | %else %put %qsysfunc(sysmsg()); 26 | %mend macdelete; 27 | -------------------------------------------------------------------------------- /maclist.sas: -------------------------------------------------------------------------------- 1 | %macro maclist 2 | /*---------------------------------------------------------------------- 3 | Generate list of compiled macros and their source directories 4 | ----------------------------------------------------------------------*/ 5 | (out=maclist /* Name of dataset to generate */ 6 | ,dlist= /* Directory list to scan (default=SASAUTOS option) */ 7 | ); 8 | /*---------------------------------------------------------------------- 9 | Produce a summary dataset with the location of the source file for the 10 | compiled macros currently in SASHELP.SASMACR catalog by scanning across 11 | SASAUTOS search path to see if corresponding file exists. 12 | 13 | When using SAS 9.2 or higher it will use to Q modifier on the SCAN() 14 | function to allow pathnames to include parentheses when properly quoted. 15 | ----------------------------------------------------------------------*/ 16 | 17 | data &out ; 18 | attrib MACRO length=$32 label='Macro name'; 19 | attrib FOUND length=3 label='Found? (0/1)'; 20 | attrib FILE length=$36 label='Filename'; 21 | attrib DNAME length=$200 label='Directory name'; 22 | keep macro found file dname; 23 | 24 | if _n_=1 then do; 25 | *----------------------------------------------------------------------; 26 | * Get SASAUTOS option into string variable and copy individual paths ; 27 | * into DLIST, expanding any filerefs that are found (such as SASAUTOS).; 28 | *----------------------------------------------------------------------; 29 | length dlist autos path $32767 ; 30 | retain dlist ; 31 | %if %length(&dlist) %then %do; 32 | autos=%sysfunc(quote(&dlist)); 33 | %end; 34 | %else %do; 35 | autos=getoption('sasautos'); 36 | %end; 37 | do i=1 by 1 until (path= ' '); 38 | %if %sysevalf(9.2 <= &sysver) %then %do; 39 | path=scan(autos,i,'( )','q'); 40 | %end; 41 | %else %do; 42 | path=scan(autos,i,'( )'); 43 | %end; 44 | if length(path) <= 8 then do; 45 | if 0=fileref(path) then path=pathname(path) ; 46 | end; 47 | dlist=left(trim(dlist)||' '||path); 48 | end; 49 | end; 50 | 51 | set sashelp.vcatalg; 52 | where libname='WORK' and memname like 'SASMAC_' 53 | and memtype='CATALOG' and objtype='MACRO'; 54 | macro=objname; 55 | file=lowcase(trim(macro))||'.sas'; 56 | 57 | *----------------------------------------------------------------------; 58 | * Scan through the paths in DLIST until file found or end of list ; 59 | *----------------------------------------------------------------------; 60 | found=0; 61 | done=0; 62 | do i=1 by 1 until (found or done); 63 | %if %sysevalf(9.2 <= &sysver) %then %do; 64 | dname=dequote(scan(dlist,i,'( )','q')); 65 | %end; 66 | %else %do; 67 | dname=dequote(scan(dlist,i,'( )')); 68 | %end; 69 | if dname=' ' then done=1; 70 | else if fileexist(trim(dname)||'/'||trim(file)) then found=1; 71 | end; 72 | if ^found then dname=' '; 73 | run; 74 | 75 | %mend maclist; 76 | -------------------------------------------------------------------------------- /missing.sas: -------------------------------------------------------------------------------- 1 | %macro missing; 2 | /*---------------------------------------------------------------------------- 3 | Return current MISSING statement settings 4 | ----------------------------------------------------------------------------*/ 5 | %local missing rc; 6 | %let rc=%sysfunc(dosubl(%nrstr( 7 | options nonotes; 8 | data _null_; 9 | missing='_ABCDEFGHIJKLMNOPQRSTUVWXYZ'; 10 | do i=1 to 27; 11 | if .=input(char(missing,i),??1.) then substr(missing,i,1)=' '; 12 | end; 13 | call symputx('missing',compress(missing,' ')); 14 | run; 15 | ))); 16 | &missing. 17 | %mend missing; 18 | -------------------------------------------------------------------------------- /mvartest.sas: -------------------------------------------------------------------------------- 1 | %macro mvartest 2 | /*---------------------------------------------------------------------- 3 | Test for the existence of a macro variable with option scope limitation 4 | ----------------------------------------------------------------------*/ 5 | (mvar /* Name of macro variable */ 6 | ,scope /* Macro scope to check (optional) */ 7 | ); 8 | /*---------------------------------------------------------------------- 9 | This macro extends the functionality of the %SYMEXIST() macro function 10 | by allowing you the check for the existance of the macro variable in 11 | a particular macro's scope. 12 | ----------------------------------------------------------------------- 13 | Usage: 14 | 15 | 1) Write a message when an expected macro variable is not defined. 16 | 17 | %if ^%mvartest(abcd) %then %put macro variable ABCD is not defined. 18 | 19 | 2) Globalize the variable that holds the returned number of 20 | observations if not defined by the user of the NOBS macro ; 21 | 22 | %macro nobs(data,mvar=); 23 | 24 | %if ^%mvartest(&mvar) %then %do; 25 | %global &mvar; 26 | %end; 27 | 28 | ... see nobs code ... 29 | 30 | %mend nobs; 31 | ------------------------------------------------------------------------ 32 | Notes: 33 | 34 | Returns a 1 when the macro variable exists and 0 otherwise. 35 | ----------------------------------------------------------------------*/ 36 | %local dsid rc where; 37 | 38 | %if %length(&scope) %then %let where=scope=%upcase("&scope"); 39 | %else %let where=scope ^= "&sysmacroname" ; 40 | %let where=name=%upcase("&mvar") and &where; 41 | 42 | %let dsid = %sysfunc(open(sashelp.vmacro(where=(&where)))); 43 | %if (&dsid) %then %do; 44 | %eval(%sysfunc(fetch(&dsid)) ^= -1) 45 | %let rc = %sysfunc(close(&dsid)); 46 | %end; 47 | %else 0; 48 | 49 | %mend mvartest; 50 | -------------------------------------------------------------------------------- /nobs.sas: -------------------------------------------------------------------------------- 1 | %macro nobs 2 | /*---------------------------------------------------------------------- 3 | Return the number of observations in a dataset reference 4 | ----------------------------------------------------------------------*/ 5 | (data /* Dataset specification (where clauses are supported) */ 6 | ,mvar=nobs /* Macro variable to store result. */ 7 | /* Set MVAR= to use NOBS as an in-line function. */ 8 | ); 9 | /*---------------------------------------------------------------------- 10 | This code is based on code developed by HOFFMAN CONSULTING as part of a 11 | FREEWARE macro tool set. Thank you Tom Hoffman. 12 | ----------------------------------------------------------------------- 13 | Usage: 14 | 15 | When the MVAR parameter is empty then NOBS will return the result as 16 | the output of the macro call so that NOBS can be called in a %IF or 17 | %LET statement. See examples. 18 | 19 | ------------------------------------------------------------------------ 20 | Examples: 21 | 22 | %* Generating the default macro variable ; 23 | %nobs(EVENTS) 24 | %if (&nobs = -1) %then %put %sysfunc(sysmsg()) ; 25 | %else %if (&nobs > 0) %then %do; 26 | .... 27 | 28 | %* Use without generating macro variable ; 29 | %if (%nobs(EVENTS,mvar=) > 0) %then %do; 30 | .... 31 | 32 | %* Generating a different macro variable and use WHERE clause ; 33 | %nobs(demog(where=(sex=1)),mvar=nmales) 34 | %put Number of males = &nmales; 35 | 36 | ------------------------------------------------------------------------ 37 | Notes: 38 | 39 | NOBS will return -1 when it cannot count the number of observations. 40 | You can use %sysfunc(sysmsg()) to get the reason. 41 | 42 | The macro variable specified in the MVAR parameter is globalized if not 43 | previously defined in the calling environment. 44 | 45 | When the DATA parameter is not specified, the last created data file is 46 | used. 47 | 48 | In the rare case that NLOBSF function cannot count the observations 49 | then the NOBS macro will loop through the dataset and count. 50 | Testing so far has found that sequential datasets such as V5 transport 51 | libraries cannot use the NLOBSF function. For large sequential datasets 52 | you will get faster results using an SQL query instead of NOBS macro. 53 | 54 | ----------------------------------------------------------------------- 55 | History: 56 | 03DEC95 TRHoffman Creation 57 | 12JUL96 TRHoffman Protected against different values of MISSING 58 | option. 59 | 20AUG97 TRHoffman Protected against case changes in options table. 60 | 11MAR99 TRHoffman Trimmed the returned macro variable. (Recommended 61 | by Paulette Staum). Used macro mvartest to globalize 62 | previously undefined variables. 63 | 25OCT2000 abernt Updated to handle lowercase letters in where clause 64 | and eight character libnames. Eliminated need to 65 | use %TRIM() macro. 66 | 14OCT03 TRHoffman Used qupcase function to permit macro variables in 67 | where clause. 68 | 09JAN2009 abernt Changed to use ATTRN functions. Test MVAR value. 69 | Return results like a function when MVAR is blank. 70 | 01MAR2018 abernt Removed usage of sasname and mvartest macros. 71 | ----------------------------------------------------------------------*/ 72 | %local dsid return ; 73 | 74 | %if %length(&mvar) %then %do; 75 | %*---------------------------------------------------------------------- 76 | MVAR parameter must be a valid variable name. 77 | -----------------------------------------------------------------------; 78 | %if not %sysfunc(nvalid(&mvar)) %then %do; 79 | %put %str( ); 80 | %put ERROR: Macro NOBS user error.; 81 | %put ERROR: "&mvar" is not a valid value for MVAR. Must be a valid SAS name.; 82 | %goto quit; 83 | %end; 84 | %*---------------------------------------------------------------------- 85 | MVAR paramater cannot duplicate a variable name used by NOBS macro. 86 | -----------------------------------------------------------------------; 87 | %if %sysfunc(indexw(DATA MVAR DSID RETURN,%upcase(&mvar))) %then %do; 88 | %put %str( ); 89 | %put ERROR: Macro NOBS user error.; 90 | %put ERROR: "&mvar" is not a valid value for MVAR. Name in use by NOBS macro.; 91 | %goto quit; 92 | %end; 93 | %*---------------------------------------------------------------------- 94 | Globalize macro variable when not defined. 95 | -----------------------------------------------------------------------; 96 | %if not %symexist(&mvar) %then %global &mvar ; 97 | %end; 98 | 99 | %*---------------------------------------------------------------------- 100 | When DATA parameter not specified, use &syslast macro variable to get 101 | last created data set. 102 | -----------------------------------------------------------------------; 103 | %if %bquote(&data) = %then %let data=&syslast; 104 | 105 | %*---------------------------------------------------------------------- 106 | DATA=_NULL_ will successfully OPEN, but cannot be queried with ATTRN 107 | function. So by setting DATA=*_NULL_* the OPEN call will fail and set 108 | an error message that can be retrieved with the SYSMSG() function. 109 | -----------------------------------------------------------------------; 110 | %if (%qupcase(&data) = _NULL_) %then %let data=*_NULL_*; 111 | 112 | %*---------------------------------------------------------------------- 113 | Initialize for failure. 114 | -----------------------------------------------------------------------; 115 | %let return=-1; 116 | 117 | %*---------------------------------------------------------------------- 118 | Open the dataset for random access. 119 | When there are no active where clauses then use NLOBS. 120 | If that did not get a count then try NLOBSF. 121 | -----------------------------------------------------------------------; 122 | %let dsid = %sysfunc(open(&data)); 123 | %if &dsid %then %do; 124 | %if not %sysfunc(attrn(&dsid,WHSTMT)) %then 125 | %let return = %sysfunc(attrn(&dsid,NLOBS)); 126 | %if (&return = -1) %then %let return = %sysfunc(attrn(&dsid,NLOBSF)); 127 | %let dsid = %sysfunc(close(&dsid)); 128 | %end; 129 | 130 | %*---------------------------------------------------------------------- 131 | If unable to get a count then try to open dataset for sequential access 132 | and count observations by fetching each one. 133 | -----------------------------------------------------------------------; 134 | %if (&return = -1) %then %do; 135 | %let dsid = %sysfunc(open(&data,IS)); 136 | %if &dsid %then %do; 137 | %let return=0; 138 | %do %while (%sysfunc(fetch(&dsid)) = 0); 139 | %let return = %eval(&return + 1); 140 | %end; 141 | %let dsid = %sysfunc(close(&dsid)); 142 | %end; 143 | %end; 144 | 145 | %*---------------------------------------------------------------------- 146 | Return the value. 147 | -----------------------------------------------------------------------; 148 | %if %length(&mvar) %then %let &mvar=&return; 149 | %else &return; 150 | 151 | %quit: 152 | %mend nobs; 153 | -------------------------------------------------------------------------------- /nvalid.sas: -------------------------------------------------------------------------------- 1 | %macro nvalid 2 | /*---------------------------------------------------------------------- 3 | A function style macro that extends the NVALID() SAS function. 4 | ----------------------------------------------------------------------*/ 5 | (string /* String to test whether it is a valid name */ 6 | ,type /* Type of name test to perform */ 7 | /* Variable names: V6 V7 ANY NLITERAL UPCASE */ 8 | /* Member name: MEMNAME COMPAT COMPATIBLE EXTEND */ 9 | /* Other names: FILEREF LIBREF FORMAT INFORMAT */ 10 | /* Default type taken from VALIDVARNAME option: V7 UPCASE ANY */ 11 | /* MEMNAME means use value of VALIDMEMNAME: COMPAT COMPATIBLE EXTEND */ 12 | ) /minoperator mindelimiter=' '; 13 | 14 | /*---------------------------------------------------------------------- 15 | A function style macro that extends the NVALID() SAS function. 16 | 17 | In addition to the name types supported by NVALID() it adds support for 18 | - V6 names (length <=8) 19 | - FORMAT/INFORMAT names 20 | - MEMBER names. 21 | 22 | Meaning of TYPE value choice: 23 | ANY - Any string from 1 to 32 characters long. 24 | V7 - Only contains Letters, digits or _ and does not start with digit. 25 | UPCASE - Same as V7 but lowercase letters not allowed. 26 | V6 - Same as V7 but max length is 8 instead of 32. 27 | NLITERAL - A name that is not V7 valid must be in NLITERAL form. 28 | FILEREF - Same as V6 with additional test if FILEREF is defined 29 | LIBREF - Same as V6 with additional test if LIBREF is defined 30 | FORMAT - Same as V7 but allow $ prefix and exclude terminal digit 31 | INFORMAT - Same as V7 but allow $ prefix and exclude terminal digit 32 | MEMNAME - Valid membername based on VALIDMEMNAME setting. 33 | COMPAT - an alias for COMPATIBLE. 34 | COMPATIBLE - Same as V7 but used for member name. 35 | EXTEND - Same as NLITERAL except there are extra excluded characters. 36 | 37 | ----------------------------------------------------------------------- 38 | Usage: 39 | 40 | %if ^%nvalid(&name) %then %put &name is not a valid SAS name.; 41 | %if ^%nvalid(&name,format) %then %put &name is not a valid format name.; 42 | %if ^%nvalid(&name,fileref) %then %put FILEREF &name is not defined. 43 | 44 | ----------------------------------------------------------------------- 45 | History: 46 | 47 | 28OCT2023 abernt Modeled after SASNAME macro from TRHoffman 48 | ----------------------------------------------------------------------*/ 49 | %local macro types return len maxl dollar; 50 | %let macro=&sysmacroname; 51 | %let types=V6 V7 ANY NLITERAL UPCASE FILEREF LIBREF FORMAT INFORMAT; 52 | %let types=&types MEMNAME COMPAT COMPATIBLE EXTEND; 53 | %let return=0; 54 | %let len=%length(&string); 55 | 56 | %*---------------------------------------------------------------------- 57 | Check that TYPE value is valid. Set default to VALIDVARNAME setting. 58 | -----------------------------------------------------------------------; 59 | %if 0=%length(&type) %then %let type=%sysfunc(getoption(validvarname)); 60 | %if not (%qupcase(&type) in &types) %then %do; 61 | %put ERROR: ¯o: &type. is not a valid value for the TYPE parameter.; 62 | %put ERROR: ¯o: Valid values are: &types ; 63 | %goto quit; 64 | %end; 65 | %else %let type=%qupcase(&type); 66 | 67 | %if &type = MEMNAME %then %do; 68 | %*---------------------------------------------------------------------- 69 | Use value of VALIDMEMNAME option as TYPE. 70 | -----------------------------------------------------------------------; 71 | %let type=%sysfunc(getoption(validmemname)); 72 | %end; 73 | 74 | %*---------------------------------------------------------------------- 75 | Set maximum length based on TYPE selected. 76 | -----------------------------------------------------------------------; 77 | %if &type in (V6 LIBREF FILEREF) %then %let maxl=8; 78 | %else %if &type in (EXTEND NLITERAL) %then %let maxl=67; 79 | %else %let maxl=32; 80 | 81 | %*---------------------------------------------------------------------- 82 | Fail when string is empty or too long. 83 | -----------------------------------------------------------------------; 84 | %if 0=&len or (&len>&maxl) %then %goto quit; 85 | 86 | %if &type in (V7 ANY NLITERAL UPCASE) %then %do; 87 | %*---------------------------------------------------------------------- 88 | For types supported by NVALID() just call it directly. 89 | -----------------------------------------------------------------------; 90 | %let return=%sysfunc(nvalid(&string,&type)); 91 | %end; 92 | %else %if &type in (FORMAT INFORMAT) %then %do; 93 | %*---------------------------------------------------------------------- 94 | FORMAT|INFORMAT - Last character cannot be a digit. Ignore leading $. 95 | -----------------------------------------------------------------------; 96 | %let dollar=%eval(%qsubstr(&string|,1,1)=$); 97 | %if &string = $ %then %let return=1; 98 | %else %if %sysfunc(notdigit(&string,&len)) %then 99 | %let return=%sysfunc(nvalid(%qsubstr(&string,1+&dollar),v7)); 100 | %end; 101 | %else %if &type = FILEREF %then %do; 102 | %*---------------------------------------------------------------------- 103 | FILEREF - Test if function of same name returns non positive value. 104 | -----------------------------------------------------------------------; 105 | %let return=%eval(%sysfunc(fileref(&string))<=0); 106 | %end; 107 | %else %if &type = LIBREF %then %do; 108 | %*---------------------------------------------------------------------- 109 | LIBREF - Test if function of same name returns zero. 110 | -----------------------------------------------------------------------; 111 | %let return=%eval(0=%sysfunc(libref(&string))); 112 | %end; 113 | %else %if &type in (V6 COMPAT COMPATIBLE) %then %do; 114 | %*---------------------------------------------------------------------- 115 | V6 - Same as V7 since length already checked. 116 | COMPAT or COMPATIBLE - Same as V7. 117 | -----------------------------------------------------------------------; 118 | %let return=%sysfunc(nvalid(&string,v7)); 119 | %end; 120 | %else %if &type = EXTEND %then %do; 121 | %*---------------------------------------------------------------------- 122 | EXTEND memname - Same as NLITERAL but some characters cannot be used. 123 | Cannot start with space or period. 124 | If it is V7 then valid. Then if NLITERAL then check if name without 125 | quotes and N has any of the extra characters. Otherwise if ANY 126 | check if the value itself has any of the extra characters. 127 | -----------------------------------------------------------------------; 128 | %if %sysfunc(nvalid(&string,v7)) %then %let return=1; 129 | %else %if %sysfunc(nvalid(&string,nliteral)) %then %do; 130 | %if ^%index(%str( .),%qsubstr(&string,2,1)) %then %let return 131 | =%eval(0=%sysfunc(indexc(%qsysfunc(dequote(&string)),"/\*?<>|:-"))); 132 | %end; 133 | %else %if ^%index(%str( .),%qsubstr(&string,1,1)) and 134 | %sysfunc(nvalid(&string,any)) %then %let return 135 | =%eval(0=%sysfunc(indexc(&string,"/\*?<>|:-"))) 136 | ; 137 | %end; 138 | 139 | %quit: 140 | &return. 141 | %mend nvalid; 142 | -------------------------------------------------------------------------------- /parmv.sas: -------------------------------------------------------------------------------- 1 | %macro parmv 2 | /*---------------------------------------------------------------------- 3 | Parameter validation with standard error message generation. 4 | ----------------------------------------------------------------------*/ 5 | (_parm /* Macro parameter name (REQ) */ 6 | ,_val= /* Space delimited list of valid values. (OPT) */ 7 | /* There are three special values of _VAL. To test for integers */ 8 | /* use either _val=POSITIVE or _val=NONNEGATIVE. To test for */ 9 | /* boolean values use _val=0 1. PARMV will accept the aliases */ 10 | /* (OFF N NO F FALSE) or (ON Y YES T TRUE) and convert them to */ 11 | /* 0 or 1, respectively. To test just for 1 and 0 then use */ 12 | /* _val=1 0 instead. */ 13 | ,_req=0 /* Is a value required? 0=No, 1=Yes. */ 14 | ,_words=0 /* Multiple values allowed? 0=No ,1=Yes */ 15 | ,_case=U /* Change case of value? U=Upper, L=Lower, N=No conversion */ 16 | ,_force=0 /* Force macro variable to exist? 0=No, 1=Yes. */ 17 | ,_defvar= /* Name of macro variable to check for a default value */ 18 | /* when parameter value not assigned by calling macro. */ 19 | ,_def= /* Default parameter value when not assigned by calling */ 20 | /* macro or by macro variable named in _defvar. */ 21 | ,_msg= /* When specified, set parmerr to 1 and writes _msg as the */ 22 | /* last error message. */ 23 | ); 24 | /*---------------------------------------------------------------------- 25 | Use the PARMV macro to validate parameter values and generate error 26 | messages in a standardized format. It can insure that values are in a 27 | consistent value by setting default values and casing. 28 | 29 | The calling macro requires local variable PARMERR to return error code. 30 | 31 | Invoke macro PARMV once for each macro parameter. After the last 32 | invocation branch to the macro's end whenever PARMERR equals 1 (e.g., 33 | %if (&parmerr) %then %goto quit;). 34 | 35 | Set the global macro variable S_PARMV to 0 to disable the error testing 36 | without disabling the case adjustment and default value setting. 37 | 38 | When _VAL=0 1 then parameter will default to 0 when empty. 39 | 40 | PARMV tool cannot be used for macros variables with names that match 41 | the parameters (_PARM _VAL _REQ _WORDS _CASE _FORCE _DEFVAR _DEF _MSG) 42 | or local macro variables (_WORD _N _VL _PL _ERROR _PARMTYP) 43 | used by PARMV itself. 44 | 45 | Use the _MSG parameter to set PARMERR to 1 and issue a message based on 46 | errors detected by criteria within the calling program. 47 | 48 | Note that for efficiency reasons, PARMV does not validate its own 49 | parameters. Only code valid values for the _PARM, _REQ, _WORDS, _CASE, 50 | and _FORCE parameters. 51 | 52 | ------------------------------------------------------------------------ 53 | Usage example: 54 | 55 | %macro test(internal,ivar,lzero,uzero,high,day,print); 56 | %local parmerr; 57 | %parmv(interval,_req=1,_words=1) 58 | %parmv(ivar,_def=period) 59 | %parmv(visit,_req=1) 60 | %if (%length(&visit) > 7) %then 61 | %parmv(visit,_msg=SAS name containing 7 or less characters) 62 | ; 63 | %parmv(lzero,_req=1) 64 | %parmv(uzero,_req=1) 65 | %parmv(high,_req=1,_val=0 1) 66 | %parmv(day,_req=1) 67 | %parmv(print,_def=1,_val=0 1) 68 | %if (&parmerr) %then %goto quit; 69 | .... 70 | %quit: 71 | %mend test; 72 | 73 | ----------------------------------------------------------------------- 74 | History: 75 | 76 | Based on original macro developed by Tom Hoffman of HOFFMAN CONSULTING. 77 | 78 | 2005-03-20 abernt Added _DEFVAR parameter. Modified final unquote step 79 | to skip unquoting if strings includes quote or dquote 80 | (in addition to % and &). 81 | 2016-08-16 abernt Rewritten to use SAS 9.4 macro enhancements 82 | 83 | ----------------------------------------------------------------------*/ 84 | %local _word _n _vl _pl _error _parmtyp; 85 | 86 | %*---------------------------------------------------------------------- 87 | Make sure return macro variables exists. 88 | -----------------------------------------------------------------------; 89 | %if ^%symexist(parmerr) %then %global parmerr; 90 | %if ^%symexist(s_msg) %then %global s_msg; 91 | %if ^%symexist(s_parmv) %then %let s_parmv=1; 92 | 93 | %*---------------------------------------------------------------------- 94 | If PARMERR has never been set then initialize it and S_MSG. 95 | -----------------------------------------------------------------------; 96 | %if (&parmerr = ) %then %do; 97 | %let parmerr = 0; 98 | %let s_msg = ; 99 | %end; 100 | 101 | %*---------------------------------------------------------------------- 102 | Quote the parameter value and calculate length for a numeric switch. 103 | 104 | When parameter does not exist then create it as local or gobal based on 105 | the setting of _FORCE. 106 | -----------------------------------------------------------------------; 107 | %if ^%length(&_parm) %then %let _pl=-1; 108 | %else %if %symexist(&_parm) %then %do; 109 | %let &_parm=%superq(&_parm); 110 | %let _pl = %length(&&&_parm); 111 | %end; 112 | %else %do; 113 | %if (&_force) %then %do; 114 | %global &_parm; 115 | %let _pl = 0; 116 | %end; 117 | %else %do; 118 | %local &_parm; 119 | %let _pl = -1; 120 | %end; 121 | %end; 122 | 123 | %*---------------------------------------------------------------------- 124 | Get length of _val to use as a numeric switch. 125 | -----------------------------------------------------------------------; 126 | %let _vl = %length(&_val); 127 | 128 | %if (&_pl=0) & %length(&_defvar) %then %if %symexist(&_defvar) %then %do; 129 | %*---------------------------------------------------------------------- 130 | Take default value from macro variable defined by _DEFVAR parameter. 131 | -----------------------------------------------------------------------; 132 | %let &_parm = %superq(&_defvar); 133 | %let _pl = %length(&&&_parm); 134 | %end; 135 | 136 | %if (&_pl=0) & %length(&_def) %then %do; 137 | %*---------------------------------------------------------------------- 138 | Take default value from _DEF parameter. 139 | -----------------------------------------------------------------------; 140 | %let _pl = %length(&_def); 141 | %let &_parm = &_def; 142 | %end; 143 | 144 | %*---------------------------------------------------------------------- 145 | Adjust CASE of the parameter value and valid values if requested. 146 | -----------------------------------------------------------------------; 147 | %if (&_pl>0) and (%qupcase(&_case) = U) %then %do; 148 | %let &_parm = %qupcase(&&&_parm); 149 | %let _val = %qupcase(&_val); 150 | %end; 151 | %else %if (&_pl>0) and (%qupcase(&_case) = L) %then %do; 152 | %if (&_pl) %then %let &_parm = %qsysfunc(lowcase(&&&_parm)); 153 | %if (&_vl) %then %let _val = %qsysfunc(lowcase(&_val)); 154 | %end; 155 | 156 | %*---------------------------------------------------------------------- 157 | Handle aliases for BOOLEAN flags. Default to 0 when not required. 158 | -----------------------------------------------------------------------; 159 | %if (&_pl>-1) and (&_val = 0 1) %then %do; 160 | %if ^(&_req) & ^(&_pl) %then %let &_parm=0; 161 | %else %if %sysfunc(indexw(OFF NO N FALSE F,%upcase(&&&_parm))) 162 | %then %let &_parm = 0; 163 | %else %if %sysfunc(indexw(ON YES Y TRUE T,%upcase(&&&_parm))) 164 | %then %let &_parm = 1; 165 | %let _pl=%length(&&&_parm); 166 | %end; 167 | 168 | %*---------------------------------------------------------------------- 169 | Bail out when no parameter validation is requested 170 | -----------------------------------------------------------------------; 171 | %if (&s_parmv = 0) %then %goto quit; 172 | 173 | %*---------------------------------------------------------------------- 174 | Initialize to no errors seen. 175 | -----------------------------------------------------------------------; 176 | %let _error = 0; 177 | 178 | %*---------------------------------------------------------------------- 179 | Error 5: _MSG specified 180 | -----------------------------------------------------------------------; 181 | %if %length(&_msg) %then %let _error = 5; 182 | 183 | %*---------------------------------------------------------------------- 184 | Error 6: Parameter does not exist 185 | -----------------------------------------------------------------------; 186 | %else %if (&_req) and (&_pl=-1) %then %let _error=6; 187 | 188 | %*---------------------------------------------------------------------- 189 | Macro variable specified by _PARM is not null. 190 | -----------------------------------------------------------------------; 191 | %else %if (&_pl>0) %then %do; 192 | 193 | %*---------------------------------------------------------------------- 194 | Error processing - parameter value not null 195 | 196 | Error 1: Invalid value - not a positive/nonnegative integer 197 | Error 2: Invalid value - not in valid list 198 | Error 3: Multiple values not allowed 199 | Error 4: Value required 200 | -----------------------------------------------------------------------; 201 | 202 | %*---------------------------------------------------------------------- 203 | Loop through possible list of words in the _PARM macro variable. 204 | ------------------------------------------------------------------------; 205 | %if ((&_vl) | ^(&_words)) %then %do; 206 | %let _n = 1; 207 | %let _word = %qscan(&&&_parm,1,%str( )); 208 | %*---------------------------------------------------------------------- 209 | Check against valid list for each word in macro parameter 210 | -----------------------------------------------------------------------; 211 | %do %while (%length(&_word)); 212 | 213 | %*---------------------------------------------------------------------- 214 | Positive integer check. 215 | -----------------------------------------------------------------------; 216 | %if (&_val = POSITIVE) %then %do; 217 | %if %sysfunc(verify(&_word,0123456789)) %then %let _error = 1; 218 | %else %if ^(&_word) %then %let _error = 1; 219 | %end; 220 | 221 | %*---------------------------------------------------------------------- 222 | Non-negative integer check. 223 | -----------------------------------------------------------------------; 224 | %else %if (&_val = NONNEGATIVE) %then %do; 225 | %if %sysfunc(verify(&_word,0123456789)) %then %let _error = 1; 226 | %end; 227 | 228 | %*---------------------------------------------------------------------- 229 | Check against valid list. Note blank padding. 230 | -----------------------------------------------------------------------; 231 | %else %if (&_vl) %then %do; 232 | %if ^%index(%str( &_val ),%str( &_word )) %then %let _error = 2; 233 | %end; 234 | 235 | %*--------------------------------------------------------------------- 236 | Get next word from parameter value 237 | -----------------------------------------------------------------------; 238 | %let _n = %eval(&_n + 1); 239 | %let _word = %qscan(&&&_parm,&_n,%str( )); 240 | 241 | %end; %* for each word in parameter value; 242 | 243 | %*---------------------------------------------------------------------- 244 | Check for multiple _words. Set error flag if not allowed. 245 | -----------------------------------------------------------------------; 246 | %if (&_n ^= 2) & ^(&_words) %then %let _error = 3; 247 | %end; %* valid not null ; 248 | 249 | %end; %* parameter value not null ; 250 | 251 | %*---------------------------------------------------------------------- 252 | Error processing - Parameter value null 253 | 254 | Error 4: Value required. 255 | -----------------------------------------------------------------------; 256 | %else %if (&_req) %then %let _error = 4; 257 | 258 | %*---------------------------------------------------------------------- 259 | Write error messages 260 | -----------------------------------------------------------------------; 261 | %if (&_error) %then %do; 262 | 263 | %*---------------------------------------------------------------------- 264 | Set PARMERR to indicate error was found. 265 | -----------------------------------------------------------------------; 266 | %let parmerr = 1; 267 | 268 | %*---------------------------------------------------------------------- 269 | Force parameter name to uppercase for clearer messages. 270 | -----------------------------------------------------------------------; 271 | %let _parm = %upcase(&_parm); 272 | 273 | %*---------------------------------------------------------------------- 274 | Call the parameter a macro variable when called from open code or when 275 | _FORCE was set. 276 | -----------------------------------------------------------------------; 277 | %if (&_force) | (1=%sysmexecdepth) %then %let _parmtyp=macro variable; 278 | %else %if (&_pl>-1) %then %let _parmtyp = parameter; 279 | 280 | %*---------------------------------------------------------------------- 281 | Write initial error message line. 282 | -----------------------------------------------------------------------; 283 | %put %str( ); 284 | %put ERROR:%str( )%sysmexecname(%sysmexecdepth-1) user error.; 285 | 286 | %if (&_error = 1) %then %do; 287 | %put ERROR: &&&_parm is not a valid value for the &_parm &_parmtyp..; 288 | %put ERROR: Only &_val integers are allowed.; 289 | %let _vl = 0; 290 | %end; 291 | 292 | %else %if (&_error = 2) %then 293 | %put ERROR: &&&_parm is not a valid value for the &_parm &_parmtyp.. 294 | ; 295 | 296 | %else %if (&_error = 3) %then %do; 297 | %put ERROR: &&&_parm is not a valid value for the &_parm &_parmtyp..; 298 | %put ERROR: The &_parm &_parmtyp may not have multiple values.; 299 | %end; 300 | 301 | %else %if (&_error = 4) %then 302 | %put ERROR: A value for the &_parm &_parmtyp is required. 303 | ; 304 | 305 | %else %if (&_error = 5) %then %do; 306 | %if (&_parm ^= ) and (&_pl) %then 307 | %put ERROR: &&&_parm is not a valid value for the &_parm &_parmtyp..; 308 | %put ERROR: &_msg..; 309 | %end; 310 | 311 | %else %if (&_error = 6) %then 312 | %put ERROR: The &_parm &_parmtyp does not exist. 313 | ; 314 | 315 | %if (&_vl) %then %do; 316 | %if (&_val = 0 1) %then 317 | %let _val=%quote(0 (or OFF NO N FALSE F) 1 (or ON YES Y TRUE T)) ; 318 | %put ERROR: Allowable values are: &_val..; 319 | %end; 320 | 321 | %if %length(&_msg) %then %let s_msg = &_msg; 322 | %else %let s_msg = Problem with%str( )%sysmexecname(%sysmexecdepth-1) &_parmtyp 323 | validation - see LOG for details.; 324 | 325 | %end; %* errors ; 326 | 327 | %quit: 328 | 329 | %*---------------------------------------------------------------------- 330 | Unquote the the parameter value, unless it contains an ampersand, per 331 | cent sign, quote, or dquote. 332 | -----------------------------------------------------------------------; 333 | %if ^%sysfunc(indexc(&&&_parm,'&%"')) %then 334 | %let &_parm = %unquote(&&&_parm); 335 | 336 | %mend parmv; 337 | -------------------------------------------------------------------------------- /parsem.sas: -------------------------------------------------------------------------------- 1 | %macro parsem 2 | /*---------------------------------------------------------------------- 3 | Macro tool for parsing a macro variable text string 4 | ----------------------------------------------------------------------*/ 5 | (_str_ /* Text string */ 6 | ,word= /* Macro variable to hold first (or last) word in _str_. */ 7 | /* When not specified, macro call resolves to this word, */ 8 | /* except when NWORDS=1. Defaults to W when strip=ALL. */ 9 | ,rest= /* Macro variable to hold remainder of string. */ 10 | ,strip=LAST /* Which word(s) to strip: LAST, FIRST, ALL */ 11 | ,nstrip=1 /* Number of words to strip */ 12 | ,delimit= /* Delimiter character. Defaults to blank. */ 13 | ,nwords=0 /* Return number of words in string? 0=No,1=as value */ 14 | /* returned by macro, OR macro variable holding the number */ 15 | /* of words. Defaults to NWORDS when strip=ALL. */ 16 | ); 17 | 18 | /*---------------------------------------------------------------------- 19 | This code was developed by HOFFMAN CONSULTING as part of a FREEWARE 20 | macro tool set. Its use is restricted to current and former clients of 21 | HOFFMAN CONSULTING as well as other professional colleagues. Questions 22 | and suggestions may be sent to TRHoffman@sprynet.com. 23 | ----------------------------------------------------------------------- 24 | Usage: 25 | 26 | In all examples, assume that the calling macro defines the &list text 27 | string. 28 | 29 | 1) Return last word as value of macro: 30 | %put %parsem(&list) is the last variable in &list; 31 | 32 | 2) Return last word as value of macro variable: 33 | %parsem(&list,word=lbyvar) 34 | %put &lbyvar is the last variable in &list; 35 | 36 | 3) Return number of words as value of macro: 37 | %put &list includes %parsem(&list,nwords=1) words; 38 | 39 | 4) Return number of words as value of macro variable: 40 | %parsem(&list,nwords=nw) 41 | %put &list includes &nw words; 42 | 43 | 5) Return all words: 44 | %unquote(%parsem(&list,strip=all) 45 | %do j=1 %to &n; 46 | word &j equals &&w&j; 47 | %end; 48 | 49 | 6) Return the first two words: 50 | %put %parsem(&list,strip=first,nstrip=2) are the first two words in &list; 51 | ------------------------------------------------------------------------ 52 | WARNING: 53 | 54 | When using PARSEM in a command line tool, use the GSUB command to 55 | submit the PARESM call. For example, gsub '%parsem(&list,nwords=nw)'. 56 | Otherwise, SAS may crash with a 'segment violation' on UNIX or a 57 | 'HOST INTERNAL ERROR: 99' on Windows. 58 | ------------------------------------------------------------------------ 59 | Notes: 60 | 61 | The WORD, REST, and NWORDS parameters cannot equal themselves. For 62 | example, %parsem(word=word) returns an error message. 63 | 64 | When the text string contains only one word, the value of &rest is null. 65 | 66 | When the text string is null, the returned macro variables will be null. 67 | 68 | The &rest variable includes any delimiters. Consecutive delimit 69 | characters are returned as a single character. 70 | 71 | When STRIP=ALL, the PARSEM macro must be called as the argument to the 72 | UNQUOTE function. If the WORD and NWORDS parameters are not set to 73 | macro variable names, then the PARSEM macro returns macro variable N, 74 | equal to the number of words, and W1,W2,...,W&n, equal to the individual 75 | word values. The names of these macro variables can be changed by 76 | specifying macro variable names for the the WORD and/or NWORDS 77 | parameters. 78 | ----------------------------------------------------------------------- 79 | History: 80 | 81 | 21JAN96 TRHoffman Creation 82 | 20APR99 TRHoffman Globalized WORD and REST when necessary. 83 | 06DEC99 TRHoffman Added NWORDS parameter. Added functionality to return 84 | all words and to return the result as value of macro 85 | rather than macro variable. 86 | 11APR00 TRHoffman Protected against empty string. Changed name of 87 | string parameter. Added support for STRIP parameter 88 | to equal number of words. 89 | 02JUN00 TRHoffman Added WARNING about command line usage. 90 | 14NOV00 TRHoffman Prevented returning last word when nwords parameter 91 | was set to a macro variable. 92 | ----------------------------------------------------------------------*/ 93 | %local macro parmerr _n _w _r _mode j _word; 94 | %let macro = parsem; 95 | 96 | %*---------------------------------------------------------------------- 97 | Validate macro parameters 98 | -----------------------------------------------------------------------; 99 | %parmv(STRIP,_req=1,_val=FIRST LAST ALL) 100 | %parmv(NSTRIP,_req=1,_val=POSITIVE) 101 | %parmv(NWORDS,_req=1) 102 | %if (&parmerr) %then %goto quit; 103 | 104 | %*---------------------------------------------------------------------- 105 | Set parameter defaults. 106 | Define _MODE and _LW variables. 107 | -----------------------------------------------------------------------; 108 | %if (&strip = ALL) %then %do; 109 | %if (&word =) %then %let word = w; 110 | %if (&nwords = 0) | (&nwords = 1) %then %let nwords = n; 111 | %let _mode = ALL; 112 | %end; 113 | %else %do; 114 | %if (&nwords = 1) %then %let _mode = NWORDS; 115 | %else %if (&word =) & (&rest =) & (&nwords = 0) %then %do; 116 | %let _mode = WORD; 117 | %let word = _word; 118 | %end; 119 | %end; 120 | 121 | %*---------------------------------------------------------------------- 122 | Set the default value of the delimiter. 123 | -----------------------------------------------------------------------; 124 | %if ^%length(&delimit) %then %let delimit = %str( ); 125 | 126 | %*---------------------------------------------------------------------- 127 | Partition string into words. 128 | -----------------------------------------------------------------------; 129 | %let _n = 0; 130 | %do %until (^%length(&&_w&_n)); 131 | %let _n = %eval(&_n + 1); 132 | %local _w&_n; 133 | %let _w&_n = %qscan(&_str_,&_n,&delimit); 134 | %end; 135 | %let _n = %eval(&_n - 1); 136 | %if (&nstrip > &_n) %then %let nstrip = &_n; 137 | 138 | %*---------------------------------------------------------------------- 139 | _mode = ALL 140 | -----------------------------------------------------------------------; 141 | %if (&_mode = ALL) %then %do; 142 | %if (%length(&word&_n) > 8) %then %do; 143 | %parmv(word,_msg=Length cannot exceed %eval(8-%length(&_n)) 144 | characters) 145 | %goto quit; 146 | %end; 147 | %nrstr(%let ) &nwords = &_n %str(;) 148 | %do j=1 %to &_n; 149 | %nrstr(%let ) &word&j = &&_w&j %str(;) 150 | %end; 151 | %goto quit; 152 | %end; 153 | 154 | %*---------------------------------------------------------------------- 155 | WORD specified 156 | -----------------------------------------------------------------------; 157 | %if (&word ^=) %then %do; 158 | %if (%upcase(&word) = WORD) %then %do; 159 | %parmv(word,_msg=WORD cannot equal itself) 160 | %goto quit; 161 | %end; 162 | %if ^%mvartest(&word) %then %do; 163 | %global &word; 164 | %end; 165 | %if (&_n = 0) %then %let &word =; 166 | %else %if (&strip = FIRST) %then %do j = 1 %to &nstrip; 167 | %if (&j = 1) %then %let &word = &_w1; 168 | %else %let &word = &&.&word.&delimit.&&_w&j; 169 | %end; 170 | %else %if (&strip = LAST) %then %do j = &_n-&nstrip+1 %to &_n; 171 | %if (&j = &_n-&nstrip+1) %then %let &word = &&_w&j; 172 | %else %let &word = &&.&word.&delimit.&&_w&j; 173 | %end; 174 | %end; 175 | 176 | %*---------------------------------------------------------------------- 177 | REST specified 178 | -----------------------------------------------------------------------; 179 | %if (&rest ^=) %then %do; 180 | %if (%upcase(&rest) = REST) %then %do; 181 | %parmv(rest,_msg=REST cannot equal itself) 182 | %goto quit; 183 | %end; 184 | %if ^%mvartest(&rest) %then 185 | %global &rest 186 | ; ; 187 | %if (&_n < 2) %then %let &rest =; 188 | %else %if (&nstrip = &_n) %then %let &rest =; 189 | %else %if (&strip = LAST) %then %do j = 1 %to &_n-&nstrip; 190 | %if (&j = 1) %then %let &rest = &_w1; 191 | %else %let &rest = &&.&rest.&delimit.&&_w&j; 192 | %end; 193 | %else %if (&strip=FIRST) %then %do j = &nstrip+1 %to &_n; 194 | %if (&j = &nstrip+1) %then %let &rest = &&_w&j; 195 | %else %let &rest = &&.&rest.&delimit.&&_w&j; 196 | %end; 197 | %end; 198 | 199 | %*---------------------------------------------------------------------- 200 | NWORDS specified 201 | -----------------------------------------------------------------------; 202 | %if ^(&nwords = 1 | &nwords = 0) %then %do; 203 | %if (%upcase(&nwords) = NWORDS) %then %do; 204 | %parmv(nwords,_msg=NWORDS cannot equal itself) 205 | %goto quit; 206 | %end; 207 | %if ^%mvartest(&nwords) %then 208 | %global &nwords 209 | ; ; 210 | %let &nwords = &_n; 211 | %end; 212 | 213 | %*---------------------------------------------------------------------- 214 | _mode = NWORDS 215 | -----------------------------------------------------------------------; 216 | %if (&_mode = NWORDS) %then 217 | &_n 218 | ; 219 | 220 | %*---------------------------------------------------------------------- 221 | _mode = WORD 222 | -----------------------------------------------------------------------; 223 | %else %if (&_mode = WORD) & (&_n ^= 0) %then 224 | &_word 225 | ; 226 | 227 | %quit: 228 | 229 | %mend parsem; 230 | -------------------------------------------------------------------------------- /qcatx.sas: -------------------------------------------------------------------------------- 1 | %macro qcatx /parmbuff; 2 | /*--------------------------------------------------------------------------- 3 | Mimic CATX() function as a macro function. Return results with macro quoting. 4 | 5 | The CAT... series of functions do not work well with %SYSFUNC() because they 6 | can accept either numeric or character values. So %SYSFUNC() has to try 7 | and figure out if the value you passed is a number or a string. Which can 8 | cause unwanted messages in the LOG and worse. 9 | 10 | Example issue with %sysfunc(catx()): 11 | 12 | 1 %put |%sysfunc(catx(^,a,,c))|; 13 | ERROR: %SYSEVALF function has no expression to evaluate. 14 | |a^c| 15 | 16 | This macro uses the PARMBUFF option to accept a virtually unlimited number 17 | of inputs. It then uses %QSCAN() to pull out the delimiter and loops over 18 | the other items emitting them when not empty. 19 | 20 | Examples: 21 | 22 | %* Examples matching CATX() example ; 23 | %put |%qcatx(^,A,B,C,D)| Expect: |A^B^C^D| ; 24 | %put |%qcatx(^,E,,F,G)| Expect: |E^F^G|; 25 | %put |%qcatx(^,H,,J)| Expect: |H^J| ; 26 | 27 | %* Spaces are preserved in delimiter but not other items ; 28 | 29 | %put |%qcatx(^, a ,b , c)| Expect: |a^b^c|; 30 | %put |%qcatx( ,a,b,c)| Expect: |a b c|; 31 | 32 | %* You can use either single or double quotes to protect commas.; 33 | %* The quotes are kept as part of the values.; 34 | 35 | %put |%qcatx(^,a,'b,b',c)| Expect: |a^'b,b'^c|; 36 | %put |%qcatx(",",a,b)| Expect: |a","b|; 37 | 38 | ---------------------------------------------------------------------------*/ 39 | %local dlm i prefix item; 40 | /*--------------------------------------------------------------------------- 41 | SYSPBUFF must have at least 5 characters, like (a,b), for any results to be 42 | produced. First remove () from around SYSPBUFF. Then take first value as 43 | the delimiter. Loop over rests of items. When not empty emit the item. 44 | Use of prefix macro variable enables only writing delimiter between items. 45 | ---------------------------------------------------------------------------*/ 46 | %if %length(&syspbuff)>4 %then %do; 47 | %let syspbuff=%qsubstr(&syspbuff,2,%length(&syspbuff)-2); 48 | %let dlm=%qscan(&syspbuff,1,%str(,),mq); 49 | %do i=2 %to %sysfunc(countw(&syspbuff,%str(,),mq)); 50 | %let item=%qsysfunc(strip(%qscan(&syspbuff,&i,%str(,),mq))); 51 | %if %length(&item) %then %do; 52 | %*;&prefix.&item. 53 | %let prefix=&dlm.; 54 | %end; 55 | %end; 56 | %end; 57 | %mend qcatx; 58 | -------------------------------------------------------------------------------- /qcompbl.sas: -------------------------------------------------------------------------------- 1 | %macro qcompbl / parmbuff; 2 | /*---------------------------------------------------------------------------- 3 | Compress multiple spaces into one, return value with macro quoting. 4 | 5 | When SYSPBUFF is shorter than 3 bytes there is no input, so do nothing. 6 | Otherwise use SYSPBUFF to generate %QUOTE() macro function call so you can 7 | pass the value to SAS function COMPBL() via %QSYSFUNC() macro function. 8 | ----------------------------------------------------------------------------*/ 9 | %if %length(&syspbuff)>2 %then %qsysfunc(compbl(%quote&syspbuff)); 10 | %mend; 11 | -------------------------------------------------------------------------------- /qlist.sas: -------------------------------------------------------------------------------- 1 | %macro qlist 2 | /*---------------------------------------------------------------------- 3 | Adds quotes to each word in a list, and optionally separate multiple 4 | words with a comma and adds parentheses to the entire string. 5 | ----------------------------------------------------------------------*/ 6 | (list /* List of words */ 7 | ,paren=1 /* Include list in parentheses? 1=Yes,0=No */ 8 | ,comma=1 /* Use comma delimiter in output list? 1=Yes,0=No */ 9 | ,delimit= /* Word delimiter for input list */ 10 | ,dsd= /* Adjacent delimiters indicate blank value? 1=Yes,0=No */ 11 | ,quote=1 /* Quote character type. 1=Single 2=Double */ 12 | ); 13 | %local rp i sep ; 14 | 15 | %*---------------------------------------------------------------------- 16 | Set COMMA to value to use as separator in the output list. 17 | -----------------------------------------------------------------------; 18 | %if "&comma" = "1" %then %let comma = ,; 19 | %else %let comma = %str( ); 20 | 21 | %*---------------------------------------------------------------------- 22 | When delimiter not specified set to a blank and default DSD to 0. 23 | -----------------------------------------------------------------------; 24 | %if ^%length(&delimit) %then %do; 25 | %let delimit = %str( ); 26 | %if ^%length(&dsd) %then %let dsd=0; 27 | %end; 28 | 29 | %*---------------------------------------------------------------------- 30 | Set DSD to value needed for COUNTW() and SCAN() function calls. 31 | -----------------------------------------------------------------------; 32 | %if "&dsd"="0" %then %let dsd=; 33 | %else %let dsd=M; 34 | 35 | %*---------------------------------------------------------------------- 36 | Set QUOTE to value needed for QUOTE() function calls. 37 | -----------------------------------------------------------------------; 38 | %if ""e"="1" %then %let quote=%str(%'); 39 | %else %let quote=%str(%"); 40 | 41 | %*---------------------------------------------------------------------- 42 | Add parentheses when requested. 43 | -----------------------------------------------------------------------; 44 | %if ("&paren" = "1") %then %do; 45 | %let rp = ); 46 | ( 47 | %end; 48 | 49 | %*---------------------------------------------------------------------- 50 | Process each word individually using the SCAN() and QUOTE() functions. 51 | -----------------------------------------------------------------------; 52 | %do i=1 %to %sysfunc(max(1,%sysfunc(countw(&list,&delimit,&dsd)))); 53 | %*;%unquote(&sep)%sysfunc(quote(%qsysfunc(scan(&list,&i,&delimit,&dsd)),"e)) 54 | %let sep=, 55 | %end; 56 | 57 | %*---------------------------------------------------------------------- 58 | Add parentheses when requested. 59 | -----------------------------------------------------------------------; 60 | %*;&rp 61 | %mend qlist; 62 | -------------------------------------------------------------------------------- /qlowcase.sas: -------------------------------------------------------------------------------- 1 | %macro qlowcase/parmbuff; 2 | %if %length(&syspbuff)>2 %then %qsysfunc(lowcase(%quote&syspbuff)); 3 | %mend; -------------------------------------------------------------------------------- /qsubstrn.sas: -------------------------------------------------------------------------------- 1 | %macro qsubstrn 2 | /*---------------------------------------------------------------------- 3 | Subset string (simulation of SUBSTRN function) 4 | ----------------------------------------------------------------------*/ 5 | (string /* String to subset */ 6 | ,position /* Start position */ 7 | ,length /* Length of substring */ 8 | ); 9 | %local len s e; 10 | %*---------------------------------------------------------------------- 11 | Get length of string. Calculate start and end positions within string. 12 | When LENGTH is not specified use length of string. 13 | ----------------------------------------------------------------------; 14 | %let len=%length(&string); 15 | %if 0=%length(&length) %then %let length=&len; 16 | %let s=%sysfunc(max(&position,1)); 17 | %let e=%sysfunc(min(&len,&length+&position-1)); 18 | %*--------------------------------------------------------------------- 19 | Use QSUBSTR to return the part of the string requested. 20 | ----------------------------------------------------------------------; 21 | %if (&s <= &len) and (&s <= &e) %then %qsubstr(&string,&s,&e-&s+1); 22 | %mend qsubstrn; 23 | -------------------------------------------------------------------------------- /qsysget.sas: -------------------------------------------------------------------------------- 1 | %macro qsysget 2 | /*---------------------------------------------------------------------- 3 | Get macro quoted value of enviroment variable 4 | ----------------------------------------------------------------------*/ 5 | (name /* Name of environment variable */ 6 | ); 7 | /*---------------------------------------------------------------------- 8 | Returns the macro quoted value of the named environment variable. 9 | 10 | The macro function %SYSGET() does not mask macro triggers. This can 11 | cause issues when the value of the environment variable contains 12 | special characters like: & % ; 13 | 14 | This macro uses the %QSYSFUNC() macro function to call the regular 15 | SAS function SYSGET() instead so macro quoting is applied. 16 | 17 | The SYSRC global macro variable is set to indicate whether the 18 | environment variable was found. 19 | 1 = Environment variable not found. 20 | 0 = Environment variable found. 21 | ----------------------------------------------------------------------*/ 22 | %let sysrc=0; 23 | %* Append %STR() to prevent %SYSFUNC() finding too few arguments. ; 24 | %if -1=%sysfunc(envlen(&name%str())) %then %let sysrc=1; 25 | %else %qsysfunc(sysget(&name)); 26 | %mend qsysget; 27 | -------------------------------------------------------------------------------- /replace_crlf.sas: -------------------------------------------------------------------------------- 1 | %macro replace_crlf 2 | /*---------------------------------------------------------------------------- 3 | Replace carriage return or linefeed characters that are inside quotes 4 | ----------------------------------------------------------------------------*/ 5 | (infile /* Fileref or quoted physical name of input file */ 6 | ,outfile /* Fileref or quoted physical name of output file */ 7 | ,cr='\r' /* Replacement string for carriage return */ 8 | ,lf='\n' /* Replacement string for linefeed */ 9 | ); 10 | /*---------------------------------------------------------------------------- 11 | SAS cannot parse delimited text files that have end of line characters in the 12 | value of a column, even if the value is quoted. This macro will read a file 13 | byte by byte and keep track of the number of quote characters seen. When the 14 | number of quotes seen is odd then the location is inside of quotes. So any 15 | carriage return or linefeed inside quotes will be replaced with the CR or LF 16 | parameter, respectively. 17 | 18 | The values of CR and LF can be anything that is valid in a PUT statement. 19 | 20 | To write nothing in place of the character just set the parameter empty: 21 | To leave one of the characters unchanged just set the hexcode as the value: 22 | CR='0D'x or LF='0A'x 23 | 24 | Examples: 25 | 26 | * Replace only CR characters ; 27 | %replace_crlf('in.csv','out.csv',lf='0A'x); 28 | 29 | * Remove CR and replace LF ; 30 | %replace_crlf('in.csv','out.csv',cr=); 31 | 32 | * Replace LF with pipe character and leave CD unchanged ; 33 | %replace_crlf('in.csv','out.csv',cr='0D'x,lf='|'); 34 | 35 | * Read from ZIP file ; 36 | %replace_crlf('myfile.zip' zip member='myfile.csv','myfile.csv') 37 | 38 | ----------------------------------------------------------------------------*/ 39 | %if 0=%length(&infile) or 0=%length(&outfile) %then %do; 40 | %put ERROR: Both the INFILE and OUTFILE parameters are required by &sysmacroname..; 41 | %put ERROR: &=infile ; 42 | %put ERROR: &=outfile ; 43 | %end; 44 | %else %do; 45 | data _null_; 46 | infile &infile recfm=f lrecl=1; 47 | file &outfile recfm=f lrecl=1; 48 | input ch $char1. ; 49 | retain q 0; 50 | q=mod(q+(ch='"'),2); 51 | if q and ch='0D'x then put &cr ; 52 | else if q and ch='0A'x then put &lf ; 53 | else put ch $char1. ; 54 | run; 55 | %end; 56 | %mend replace_crlf; 57 | -------------------------------------------------------------------------------- /safe_ds2csv.sas: -------------------------------------------------------------------------------- 1 | %macro safe_ds2csv 2 | /*---------------------------------------------------------------------- 3 | Write SAS dataset as CSV file insuring proper quoting 4 | ----------------------------------------------------------------------*/ 5 | (dsn /* Input dataset name. DSNoptions allowed */ 6 | ,outfile=csv /* Output fileref or quoted filenam. Can include options */ 7 | /* valid on a FILE statement except the options */ 8 | /* DSD, DLM= and TERMSTR= generated by the macro. */ 9 | ,dlm=',' /* Delimiter character as string literal */ 10 | ,names=1 /* Write header row? (0/1) */ 11 | ,label=0 /* Use LABEL instead of NAME for header row? (0/1) */ 12 | ,format= /* Optional format overrides. */ 13 | ,maxchar=300 /* Optional max length for any formatted value max=32767 */ 14 | ); 15 | /*---------------------------------------------------------------------- 16 | Write dataset to a delimited file that SAS should be able to read back. 17 | 18 | All lines will end with the Windows/DOS standard CR+LF sequence so that 19 | SAS can read values that have embedded CR or LF characters. Any CR+LF 20 | two byte sequences in the data will be written as just a CR. 21 | 22 | Also to prevent SAS from getting confused by single quote values in two 23 | different fields on the same line from looking like one long quoted 24 | string any value with a single quote will be quoted in the output file. 25 | 26 | Uses the CALL VNEXT() idea originally posted online by data_null_ in 27 | many places. For example look at this thread on SAS Communities 28 | https://communities.sas.com/t5/Base-SAS-Programming/Output-to-delimited-format/m-p/292767#M60829 29 | 30 | To avoid any name conflicts uses temporary arrays named _CHAR_ and 31 | _CHARACTER_ which SAS allows as array names but not as variable names. 32 | 33 | Notes: 34 | - To pass a physical name for a file enclose it in quotes. 35 | - To pass a different delimiter use a string literal. 36 | - You can use hex literal for delimiter. '09'x is a TAB character. 37 | - To suppress header row use NAMES=0. 38 | - To use LABEL instead of NAME in header row use LABEL=1. 39 | - Do not include the FORMAT keyword in the FORMAT= option. 40 | - To prevent line wrapping with datasets that have long values or a 41 | large number of variables include LRECL= option in OUTFILE. 42 | - Use the TERMSTR=CRLF option when reading the resulting file to make 43 | sure that embedded CR or LF characters will not cause the input to 44 | get confused. 45 | - Set MAXCHAR long enough for the largest value of any variable. Note 46 | that you will get improved performance by using a smaller value for MAXCHAR 47 | since the perfomance of string operations is much worse for long 48 | strings. 49 | 50 | Examples: 51 | * Create CSV file ; 52 | %safe_ds2csv(mydataset,outfile='myfile.csv' lrecl=50000) 53 | 54 | * Read generated CSV using TERMSTR=CRLF option; 55 | data mydataset; 56 | infile 'myfile.csv' dsd lrecl=50000 truncover firstobs=2 termstr=crlf ; 57 | input .... ; 58 | run; 59 | 60 | ----------------------------------------------------------------------*/ 61 | %local optsave; 62 | %let optsave="%qsysfunc(getoption(missing))"; 63 | options missing=' '; 64 | *----------------------------------------------------------------------------; 65 | * Write data values to delimited text file using PUT statements. ; 66 | *----------------------------------------------------------------------------; 67 | data _null_; 68 | set &dsn; 69 | format &format; 70 | array _char_(1) $32 _temporary_; 71 | array _character_(1) $&maxchar. _temporary_; 72 | file &outfile dsd dlm=&dlm 73 | %if %qupcase(&outfile) ne LOG %then termstr=crlf; 74 | ; 75 | %if (&names) %then %do; 76 | *----------------------------------------------------------------------------; 77 | * Write the header row ; 78 | *----------------------------------------------------------------------------; 79 | if _n_ eq 1 then do; 80 | do while(1); 81 | call vnext(_char_(1)); 82 | if _char_(1) = '_ERROR_' then leave; 83 | _character_(1) = 84 | %if (&label) %then vlabelx(_char_(1)); 85 | %else _char_(1); 86 | ; 87 | link write; 88 | end; 89 | put; 90 | end; 91 | %end; 92 | _char_(1)=' '; 93 | do while(1); 94 | call vnext(_char_(1)); 95 | if _char_(1) = '_ERROR_' then leave; 96 | _character_(1) = vvaluex(_char_(1)); 97 | link write; 98 | end; 99 | put; 100 | return; 101 | *----------------------------------------------------------------------------; 102 | * Write one value. Force quotes when it contains single quote, CR or LF ; 103 | * Replace any CRLF pair with CR only ; 104 | *----------------------------------------------------------------------------; 105 | write: 106 | if indexc(_character_(1),"'",'0D0A'x) then do; 107 | _character_(1)= tranwrd(_character_(1),'0D0A'x,'0D'x); 108 | put _character_(1) ~ @; 109 | end; 110 | else put _character_(1) @; 111 | return; 112 | run; 113 | options missing=&optsave; 114 | %mend safe_ds2csv; 115 | -------------------------------------------------------------------------------- /sas2xport.sas: -------------------------------------------------------------------------------- 1 | %macro sas2xport 2 | /*---------------------------------------------------------------------- 3 | Generate SAS V5 or V9 Transport file from SAS datasets/views 4 | ----------------------------------------------------------------------*/ 5 | (data /* Name of dataset to export */ 6 | ,file /* Output Fileref or quoted physical file name */ 7 | ,libref= /* Source library reference */ 8 | ,select= /* Space delimited list of member names */ 9 | ,exclude= /* Space delimited list of member names */ 10 | ,version= /* XPORT format to generate V5|V9 (Default=V9) */ 11 | ,append= /* Append to existing file? (0/1) (Default=0) */ 12 | ,out=_null_ /* Optional dataset to write contents information */ 13 | )/minoperator mindelimiter=' '; 14 | /*---------------------------------------------------------------------- 15 | Generate SAS V5 or V9 Transport file from SAS datasets/views. 16 | 17 | Either specify a single input dataset using the DATA parameter or use 18 | the LIBREF parameter with optional SELECT and/or EXCLUDE parameters to 19 | indicate the dataset(s) to export. 20 | 21 | Allows alias of 5, 6, or V6 for V5 XPORT format. 22 | Allows alias of 7, 8, 9, V7, or V8 for V9 XPORT format. 23 | 24 | For specification of the structure of SAS V5 transport files see: 25 | SAS technical support notice TS-140 26 | For specification on V9 format see: 27 | https://support.sas.com/kb/46/944.html 28 | 29 | The XPORT engine will read V5 transport files with character variables 30 | with storage length larger than 200. It also will read non-standard 31 | variable names and does not convert names to uppercase. 32 | 33 | This macro will take advantage of that to generate V5 transport files 34 | for datasets that the XPORT engine cannot handle. 35 | 36 | When forcing V5 XPORT generation long names will be truncated. Also digits 37 | will be appended when necessary to create unique names. Long labels will be 38 | truncated. Some labels may be replaced by the original long variable name. 39 | Long format/informat names will be truncated. 40 | 41 | The original long variable name will be written (at location used by V9 42 | xport format). This is ignored by the XPORT engine and %xpt2loc() macro 43 | but will be used by the %XPORT2SAS() macro. 44 | 45 | Enhancements over the XPORT libname engine include: 46 | - Support for creating the enhanced V9 XPORT file format. 47 | - Character variables longer than 200 allowed in V5 XPORT file. 48 | - Mixed case variable names preserved in V5 XPORT file. 49 | - Dataset creation and modification time stamps preserved. 50 | - Automatic rename of long variable names with name collision detection. 51 | 52 | For long format and/or informat names it will just truncate the name. 53 | 54 | ------------------------------------------------------------------------ 55 | Usage Notes: 56 | - &SYSRC will be set to 0 on success and 1 when there are problems. 57 | - If FILE is not specified then it will generate a physical filename 58 | using the membername in DATA parameter or the LIBREF parameter. 59 | - When names are truncated the original name is stored in the label. 60 | - Truncation of member names, format names and informat names could 61 | cause them to become non-unique. 62 | - Use LIBREF, SELECT and/or EXCLUDE options to export multiple datasets. 63 | - Use APPEND=1 to add additional dataset(s) to an existing XPORT file. 64 | - Macro calls itself recursively with APPEND=1 when multiple datasets 65 | are selected. 66 | 67 | ----------------------------------------------------------------------- 68 | Examples: 69 | * Single dataset to physical filename ; 70 | %sas2xport(sashelp.class,file="class.xpt") 71 | 72 | * Use automatic output filename ; 73 | %sas2xport(sashelp.class) 74 | 75 | * All datasets from a libref ; 76 | libname sasdata "."; 77 | filename export "sasdata.xpt"; 78 | %sas2xport(libref=sasdata,file=export) 79 | 80 | * List of datasets to fileref ; 81 | filename export "class.xpt"; 82 | %sas2xport(libref=sashelp,select=class cars,file=export) 83 | 84 | ----------------------------------------------------------------------*/ 85 | %local parmerr hdr1 hdr2 note warn err nobs did dslist nds i 86 | member selectp excludep fname rc fexist obslen 87 | ; 88 | %let parmerr=0; 89 | %let hdr1=HEADER RECORD*******; 90 | %let hdr2=HEADER RECORD!!!!!!!; 91 | %let note=putlog "NOTE: &sysmacroname: "; 92 | %let warn=putlog 'WARN' "ING: &sysmacroname: "; 93 | %let err=putlog 'ERR' "OR: &sysmacroname: "; 94 | %let nobs=000000000000000; 95 | 96 | %*---------------------------------------------------------------------- 97 | Validate parameters. 98 | -----------------------------------------------------------------------; 99 | /*---------------------------------------------------------------------- 100 | Check DATA and LIBREF settings. When neither is set use &SYSLAST. 101 | ----------------------------------------------------------------------*/ 102 | %if 0=%length(&data.&libref) %then %let data=&syslast; 103 | /*---------------------------------------------------------------------- 104 | When DATA is set then ignore LIBREF. 105 | ----------------------------------------------------------------------*/ 106 | %else %if %length(&data) and %length(&libref) %then %do; 107 | %put NOTE: &sysmacroname: &=DATA was specified so ignoring &=LIBREF..; 108 | %let libref=; 109 | %end; 110 | /*---------------------------------------------------------------------- 111 | Make sure DATA can be found. 112 | ----------------------------------------------------------------------*/ 113 | %if %length(&data) %then %do; 114 | %let did=%sysfunc(open(&data)); 115 | %if 0=&did %then %do; 116 | %put ERROR: &sysmacroname: Unable to open &=data..; 117 | %let parmerr=1; 118 | %end; 119 | %else %let did=%sysfunc(close(&did)); 120 | %end; 121 | /*---------------------------------------------------------------------- 122 | When LIBREF is set then make sure LIBREF is valid. And standardize 123 | values of SELECT and EXCLUDE for use in SQL query. 124 | ----------------------------------------------------------------------*/ 125 | %if %length(&libref) %then %do; 126 | %if %length(&libref)>8 or not %sysfunc(nvalid(&libref,v7)) %then %do; 127 | %put ERROR: &sysmacroname: &=libref is not a valid SAS name.; 128 | %let parmerr=1; 129 | %end; 130 | %else %if %sysfunc(libref(&libref)) %then %do; 131 | %put ERROR: &sysmacroname: &=libref is not defined.; 132 | %let parmerr=1; 133 | %end; 134 | %else %let libref=%upcase(&libref); 135 | %if (%qupcase(&select) = _ALL_) %then %let select=; 136 | %else %do i=1 %to %sysfunc(countw(&select,%str( ),q)); 137 | %let member=%qscan(&select,&i,%str( ),q); 138 | %if %sysfunc(nvalid(&member,nliteral)) %then %do; 139 | %let selectp=&selectp|%qsysfunc(dequote(&member)); 140 | %end; 141 | %else %do; 142 | %put ERROR: &sysmacroname: &=member is not valid name to SELECT.; 143 | %let parmerr=1; 144 | %end; 145 | %end; 146 | %do i=1 %to %sysfunc(countw(&exclude,%str( ),q)); 147 | %let member=%qscan(&exclude,&i,%str( ),q); 148 | %if %sysfunc(nvalid(&member,nliteral)) and (%qupcase(&member) ne _ALL_) 149 | %then %let excludep=&excludep|%qsysfunc(dequote(&member)) 150 | ; 151 | %else %do; 152 | %put ERROR: &sysmacroname: &=member is not valid name to EXCLUDE.; 153 | %let parmerr=1; 154 | %end; 155 | %end; 156 | %end; 157 | 158 | %*---------------------------------------------------------------------- 159 | Make sure DATA did not end up being _NULL_. 160 | -----------------------------------------------------------------------; 161 | %if (%qupcase(&data)=_NULL_) %then %do; 162 | %put ERROR: &sysmacroname: &=data is not valid. Cannot export the null dataset.; 163 | %let parmerr=1; 164 | %end; 165 | 166 | %*---------------------------------------------------------------------- 167 | Make sure APPEND is valid 0 1 boolean. Default to 0. 168 | -----------------------------------------------------------------------; 169 | %if 0=%length(&append) %then %let append=0; 170 | %else %if %qupcase(&append) in Y YES T TRUE ON 1 %then %let append=1; 171 | %else %if %qupcase(&append) in N NO F FALSE OFF 0 %then %let append=0; 172 | %else %do; 173 | %put ERROR: &sysmacroname: &=append is not a valid value. Valid values are: 174 | %quote(0 (or OFF NO N FALSE F) 1 (or ON YES Y TRUE T)); 175 | %let parmerr=1; 176 | %end; 177 | 178 | %*---------------------------------------------------------------------- 179 | Make sure VERSION is valid and standardize to V5 or V9. 180 | -----------------------------------------------------------------------; 181 | %if 0=%length(&version) %then %let version=V9; 182 | %else %if %qupcase(&version) in 7 8 9 V7 V8 V9 %then %let version=V9; 183 | %else %if %qupcase(&version) in 5 6 V5 V6 %then %let version=V5; 184 | %else %do; 185 | %put ERROR: &sysmacroname: &=version is not valid. Valid values are V5 or V9; 186 | %let parmerr=1; 187 | %end; 188 | 189 | %if (&parmerr) %then %goto quit; 190 | 191 | %if %length(&libref) %then %do; 192 | *----------------------------------------------------------------------; 193 | * Calculate matching list of datasets from DICTIONARY.MEMBERS ; 194 | *----------------------------------------------------------------------; 195 | proc sql noprint ; 196 | select memname,catx('.',libname,nliteral(memname)) 197 | into :dslist,:dslist separated by '|' 198 | from dictionary.members 199 | where memtype in ('DATA','VIEW') and libname="&libref" 200 | %if %length(&select) %then and findw("&selectp",memname,'|','iors') ; 201 | %if %length(&exclude) %then and 0=findw("&excludep",memname,'|','iors'); 202 | order by 1 203 | ; 204 | quit; 205 | %let nds=&sqlobs; 206 | %let data=%scan(&dslist,1,|); 207 | %end; 208 | %else %let nds=1; 209 | %if (&nds<1) %then %do; 210 | %put ERROR: &sysmacroname: No datasets selected. &=libref &=select &=exclude; 211 | %let parmerr=1; 212 | %end; 213 | 214 | %if (&parmerr) %then %goto quit; 215 | 216 | %*---------------------------------------------------------------------- 217 | Default value for FILE. 218 | -----------------------------------------------------------------------; 219 | %if 0=%length(&file) %then %do; 220 | %if %length(&libref) %then %let fname=&libref; 221 | %else %let fname=%scan(&data,-1,. .); 222 | %let fname="%sysfunc(lowcase(&fname)).xpt"; 223 | %put NOTE: &sysmacroname: Since no file specified will use FILE=&fname..; 224 | %end; 225 | %else %let fname=&file; 226 | 227 | %*---------------------------------------------------------------------- 228 | Test if FILE exists. 229 | -----------------------------------------------------------------------; 230 | %if %sysfunc(nvalid(&fname,v7)) and %length(&fname)<=8 %then %do; 231 | %let rc=%sysfunc(fileref(&fname)); 232 | %if (&rc = 0) %then %let fexist=1; 233 | %else %if (&rc<0) %then %let fexist=0; 234 | %end; 235 | %if (&fexist=) %then %do; 236 | %let fname=%sysfunc(quote(%qsysfunc(dequote(&fname)),%str(%'))); 237 | %let fexist=%sysfunc(fileexist(&fname)); 238 | %end; 239 | 240 | %if not %sysfunc(cexist(work.formats.xprtflt.format)) %then %do; 241 | *----------------------------------------------------------------------; 242 | * Create XPRTFLT format to write numeric missing values properly. ; 243 | *----------------------------------------------------------------------; 244 | proc format; 245 | value xprtflt . ='2E00000000000000'x 246 | .A ='4100000000000000'x .B ='4200000000000000'x .C ='4300000000000000'x 247 | .D ='4400000000000000'x .E ='4500000000000000'x .F ='4600000000000000'x 248 | .G ='4700000000000000'x .H ='4800000000000000'x .I ='4900000000000000'x 249 | .J ='4A00000000000000'x .K ='4B00000000000000'x .L ='4C00000000000000'x 250 | .M ='4D00000000000000'x .N ='4E00000000000000'x .O ='4F00000000000000'x 251 | .P ='5000000000000000'x .Q ='5100000000000000'x .R ='5200000000000000'x 252 | .S ='5300000000000000'x .T ='5400000000000000'x .U ='5500000000000000'x 253 | .V ='5600000000000000'x .W ='5700000000000000'x .X ='5800000000000000'x 254 | .Y ='5900000000000000'x .Z ='5A00000000000000'x ._ ='5F00000000000000'x 255 | other=(|s370frb8.|) 256 | ; 257 | run; 258 | %end; 259 | 260 | %*---------------------------------------------------------------------- 261 | If APPEND=0 or FILE does not exist then write LIBRARY header. 262 | -----------------------------------------------------------------------; 263 | %if (0=&append or 0=&fexist) %then %do; 264 | data _null_; 265 | file &fname recfm=n; 266 | %if (&version = V5) %then %do; 267 | put "&hdr1.LIBRARY &hdr2." 30*'0' ' '; 268 | %end; %else %do; 269 | put "&hdr1.LIBV8 &hdr2." 30*'0' ' '; 270 | %end; 271 | date=datetime(); 272 | sas='SAS'; saslib='SASLIB'; sysver="&sysver"; sysscp="&sysscp"; 273 | put (sas sas saslib sysver sysscp) ($8.) 24*' ' date datetime16. ; 274 | put date datetime16. 64*' '; 275 | run; 276 | %end; 277 | 278 | %*---------------------------------------------------------------------------- 279 | Use DATA step to gather dataset contents information. 280 | Generate MEMBER, DSCRPTR, NAMESTR, LABEL and OBS records. 281 | Use CALL EXECUTE() to generate CODE to write the data records. 282 | -----------------------------------------------------------------------------; 283 | data &out; 284 | %*---------------------------------------------------------------------------- 285 | Use MODIFY statement when OUT exists and APPEND is requested. 286 | -----------------------------------------------------------------------------; 287 | %if &append and %sysfunc(exist(&out)) %then %do; 288 | modify &out; 289 | %end; 290 | %*---------------------------------------------------------------------------- 291 | Define variables. 292 | -----------------------------------------------------------------------------; 293 | length 294 | LIBNAME $8 MEMNAME $32 295 | VARNUM 8 UNAME $8 NAME $32 LENGTH 8 TYPEN 8 TYPE $4 TYPEF $8 296 | FORMAT $49 INFORMAT $49 297 | FORMATN $32 FORMATL FORMATD 8 298 | INFORMN $32 INFORML INFORMD 8 299 | LABEL $256 300 | NVAR 8 NOBS 8 OBSLEN 8 CRDATE 8 MODATE 8 301 | TYPEMEM $8 MEMTYPE $8 302 | MEMLABEL $256 303 | ; 304 | keep libname -- memlabel; 305 | format crdate modate datetime19.; 306 | length firstvar suffix 8; 307 | 308 | dsid=open(symget('data')); 309 | %*---------------------------------------------------------------------------- 310 | Create HASH() objects. U - unique name V - all variables ITER - hiter for V 311 | -----------------------------------------------------------------------------; 312 | declare hash u(); 313 | u.definekey('uname'); 314 | u.definedata('firstvar'); 315 | u.definedone(); 316 | declare hash v(ordered:'Y'); 317 | v.definekey('varnum'); 318 | v.definedata('libname','memname','typemem' 319 | ,'varnum','uname','name','length','typen','type','typef' 320 | ,'format','formatn','formatl','formatd' 321 | ,'informat','informn','informl','informd' 322 | ,'label','nobs','nvar','crdate','modate','memlabel'); 323 | v.definedone(); 324 | declare hiter iter('v'); 325 | 326 | %*---------------------------------------------------------------------------- 327 | Read member information using ATTRN() and ATTRC() functions. 328 | -----------------------------------------------------------------------------; 329 | nvar=attrn(dsid,'nvars'); 330 | nobs=attrn(dsid,'nlobsf'); 331 | crdate=attrn(dsid,'crdte'); 332 | modate=attrn(dsid,'modte'); 333 | libname=attrc(dsid,'lib'); 334 | memname=attrc(dsid,'mem'); 335 | memlabel=attrc(dsid,'label'); 336 | typemem=attrc(dsid,'type'); 337 | memtype=attrc(dsid,'mtype'); 338 | %*---------------------------------------------------------------------------- 339 | Find all variable names and info. Skip variables where VARNAME() is empty. 340 | Empty VARNAME() is caused by DROP=/KEEP= dataset options. 341 | 342 | Store variable informat into hash() objects V and U. 343 | -----------------------------------------------------------------------------; 344 | suffix=0; 345 | do index=1 by 1 while (varnum 8; 384 | l_format + length(formatn) > 8; 385 | l_inform + length(informn) > 8; 386 | l_label + length(label) > 40; 387 | nl_label + (length(formatn)>8 or length(informn)>8 or length(label)>40); 388 | l_length + length>200; 389 | end; 390 | end; 391 | dsid=close(dsid); 392 | %if (&version = V5) %then %do; 393 | %*---------------------------------------------------------------------------- 394 | Iterate over variables again so that can : 395 | - generate unique short names 396 | - fix case of uname to match case of name 397 | -----------------------------------------------------------------------------; 398 | rc = iter.first(); 399 | do while (rc = 0); 400 | u.find(); 401 | if varnum ne firstvar then do; 402 | firstvar=varnum; 403 | do suffix=0 by 1 while(u.add()); 404 | uname=cats(substr(uname,1,8-length(cats(suffix+1))),suffix+1); 405 | end; 406 | end; 407 | if suffix<1 then uname=substr(name,1,length(uname)); 408 | else uname=cats(substr(name,1,length(uname)-length(cats(suffix))),suffix); 409 | if uname ne name then do; 410 | ¬e 'Name ' name :$quote. 'converted to ' uname :$quote. 'and ' @; 411 | if label ne ' ' then putlog 'label replaced with original name.'; 412 | else putlog 'original name stored in label.'; 413 | label=name; 414 | end; 415 | v.replace(); 416 | rc = iter.next(); 417 | end; 418 | %end; 419 | %else %do; 420 | uname=name; 421 | %end; 422 | *----------------------------------------------------------------------; 423 | * Write Member Headers ; 424 | *----------------------------------------------------------------------; 425 | file &fname recfm=n mod ; 426 | sas='SAS'; 427 | sasdata='SASDATA'; 428 | sasver="&sysver"; 429 | osver="&sysscp"; 430 | if missing(crdate) then crdate=datetime(); 431 | if memtype='VIEW' then modate=datetime(); 432 | if missing(modate) then modate=crdate; 433 | if length(memlabel)>40 then ¬e 'Truncating member label for ' 434 | memname= 'to 40 characters: ' memlabel $40. ; 435 | %if (&version = V5) %then %do; 436 | if length(memname)>8 then do; 437 | ¬e 'Member ' memname :$quote. 'saved as ' memname :$8. 438 | +(-1) '. Original name stored as member label.' @; 439 | if memlabel ne ' ' then putlog ' Original label was: ' memlabel :$quote.; 440 | else putlog; 441 | memlabel=memname; 442 | end; 443 | put "&hdr1.MEMBER &hdr2." 17*'0' '1600000000140 '; 444 | put "&hdr1.DSCRPTR &hdr2." 30*'0' ' '; 445 | put (sas memname sasdata sasver osver) ($8.) 24*' ' crdate datetime16. ; 446 | %end; %else %do; 447 | put "&hdr1.MEMBV8 &hdr2." 17*'0' '1600000000140 '; 448 | put "&hdr1.DSCPTV8 &hdr2." 30*'0' ' '; 449 | put sas $8. memname $32. (sasdata sasver osver) ($8.) crdate datetime16. ; 450 | %end; 451 | put modate datetime16. 16*' ' memlabel $40. typemem $8. ; 452 | 453 | *----------------------------------------------------------------------; 454 | * Write Variable list ; 455 | *----------------------------------------------------------------------; 456 | %if (&version = V5) %then %do; 457 | put "&hdr1.NAMESTR &hdr2." nvar z10. 20*'0' ' ' ; 458 | %end; %else %do; 459 | put "&hdr1.NAMSTV8 &hdr2." nvar z10. 20*'0' ' ' ; 460 | %end; 461 | zero=0; 462 | rc = iter.first(); 463 | do while (rc = 0); 464 | put (typen zero length varnum ) (s370fib2.) uname $8. label $40. ; 465 | put formatn $8. (formatl formatd zero zero) (s370fib2.) ; 466 | put informn $8. (informl informd) (s370fib2.) ; 467 | put obslen s370fib4.; 468 | labellen=lengthn(label); 469 | formatlen=lengthn(formatn); 470 | informlen=lengthn(informn); 471 | put name $32. (labellen formatlen informlen) (s370fib2.) 14*'00'x ; 472 | obslen+length; 473 | rc = iter.next(); 474 | end; 475 | call symputx('obslen',obslen); 476 | do _n_=1 to 80*ceil(140*nvar/80)-140*nvar; 477 | put ' '; 478 | end; 479 | 480 | *----------------------------------------------------------------------; 481 | * Write LABELV9 records ; 482 | *----------------------------------------------------------------------; 483 | if nl_label and ("&version"="V9") then do; 484 | put "&hdr1.LABELV9 &hdr2." nl_label 12.-L 18*'0' ' ' ; 485 | bytes=0; 486 | rc = iter.first(); 487 | do while (rc = 0); 488 | if length(label)>40 or length(formatn)>8 or length(informn)>8 then do; 489 | lenname=length(name); 490 | lenlabel=length(label); 491 | lenform=length(format); 492 | leninf =length(informat); 493 | put (varnum lenname lenlabel lenform leninf) (s370fib2.) 494 | name $varying32. lenname label $varying256. lenlabel 495 | format $varying49. lenform informat $varying49. leninf 496 | ; 497 | bytes+10+lenname+lenlabel+lenform+leninf ; 498 | end; 499 | rc = iter.next(); 500 | end; 501 | do _n_=1 to 80*ceil(bytes/80)-bytes; put ' '; end; 502 | end; 503 | 504 | *----------------------------------------------------------------------; 505 | * Write OBS record ; 506 | *----------------------------------------------------------------------; 507 | %if (&version = V5) %then %do; 508 | put "&hdr1.OBS &hdr2." 30*'0' ' '; 509 | %end; %else %do; 510 | put "&hdr1.OBSV8 &hdr2." nobs 15.-L 15*'0' ' '; 511 | %end; 512 | 513 | %*---------------------------------------------------------------------- 514 | Use CALL EXECUTE to generate and run DATA step to write the data. 515 | -----------------------------------------------------------------------; 516 | call execute('data _null_;'); 517 | call execute("if eof then call symputx('nobs',_n_-1,'L');"); 518 | call execute('file '||symget('fname')||' recfm=n mod;'); 519 | call execute('set '||catx('.',libname,nliteral(memname))||' end=eof;'); 520 | call execute('put '); 521 | length fmtspec $12; 522 | rc = iter.first(); 523 | do while (rc = 0); 524 | if typen=1 then fmtspec=cats('xprtflt',length,'.'); 525 | else fmtspec=cats('$ascii',length,'.'); 526 | call execute(catx(' ',nliteral(name),fmtspec)); 527 | rc = iter.next(); 528 | end; 529 | call execute(';run;'); 530 | 531 | %*---------------------------------------------------------------------------- 532 | Write note(s) to SAS log about member being written. 533 | -----------------------------------------------------------------------------; 534 | ¬e "Writing " libname +(-1) '.' memname "to xport file."; 535 | %if (&version=V5) %then %do; 536 | ¬e 'Member has ' l_name 'long variable names.'; 537 | if ndups then ¬e 'Member has ' ndups 'long names that caused conflicts.'; 538 | ¬e 'Member has ' l_format 'long format names.'; 539 | ¬e 'Member has ' l_inform 'long informat names.'; 540 | ¬e 'Member has ' l_label 'long variable labels.'; 541 | ¬e 'Member has ' l_length 'long character variables.'; 542 | %end; 543 | %*---------------------------------------------------------------------------- 544 | Save variable metadata to &OUT dataset. 545 | -----------------------------------------------------------------------------; 546 | rc = iter.first(); 547 | do while (rc = 0); 548 | output; 549 | rc = iter.next(); 550 | end; 551 | stop; 552 | run; 553 | 554 | *----------------------------------------------------------------------; 555 | * Pad with spaces to an even multiple of 80 bytes. ; 556 | *----------------------------------------------------------------------; 557 | data _null_; 558 | file &fname recfm=n mod; 559 | do _n_=1 to 80*ceil(&obslen*&nobs/80)-&obslen*&nobs; 560 | put ' '; 561 | end; 562 | run; 563 | 564 | %do i=2 %to &nds ; 565 | *----------------------------------------------------------------------; 566 | * Recursively call SAS2XPORT macro to append next dataset. ; 567 | *----------------------------------------------------------------------; 568 | %sas2xport(%scan(&dslist,&i,|),file=&file,append=1,version=&version); 569 | %if &sysrc %then %goto quit; 570 | %end; 571 | 572 | %quit: 573 | %let sysrc=%eval(&parmerr or &sysrc); 574 | %mend sas2xport; 575 | -------------------------------------------------------------------------------- /squote.sas: -------------------------------------------------------------------------------- 1 | %macro squote(value); 2 | %if %sysevalf(&sysver < 9.3) %then 3 | %unquote(%str(%')%qsysfunc(tranwrd(%superq(value),%str(%'),''))%str(%')) 4 | ; 5 | %else %sysfunc(quote(%superq(value),%str(%'))) ; 6 | %mend squote; 7 | -------------------------------------------------------------------------------- /subnet.sas: -------------------------------------------------------------------------------- 1 | %macro subnet(in=,out=,from=from,to=to,subnet=subnet,directed=1); 2 | /*---------------------------------------------------------------------- 3 | SUBNET - Build connected subnets from pairs of nodes. 4 | Input Table :FROM TO pairs of rows 5 | Output Table:input data with &subnet added 6 | Work Tables: 7 | NODES - List of all nodes in input. 8 | NEW - List of new nodes to assign to current subnet. 9 | 10 | Algorithm: 11 | Pick next unassigned node and grow the subnet by adding all connected 12 | nodes. Repeat until all unassigned nodes are put into a subnet. 13 | 14 | To treat the graph as undirected set the DIRECTED parameter to 0. 15 | ----------------------------------------------------------------------*/ 16 | %local subnetid next getnext ; 17 | %*---------------------------------------------------------------------- 18 | Initialize subnet id counter. 19 | -----------------------------------------------------------------------; 20 | %let subnetid=0; 21 | proc sql noprint; 22 | *----------------------------------------------------------------------; 23 | * Create list of all nodes ; 24 | *----------------------------------------------------------------------; 25 | create table nodes as 26 | select . as subnet, &from as node from &in where &from is not null 27 | union 28 | select . as subnet, &to as node from &in where &to is not null 29 | ; 30 | *----------------------------------------------------------------------; 31 | * Generate query to get next unassigned node into a macro variable. ; 32 | *----------------------------------------------------------------------; 33 | %*---------------------------------------------------------------------- 34 | Query is modified based on type of variable used for node. This query 35 | is put into a macro variable so it can be used twice in the program. 36 | -----------------------------------------------------------------------; 37 | select catx(' ','select ',case when type='num' then 'node' 38 | else 'quote(trim(node),"''")' end 39 | ,'into :next from nodes where subnet=.') 40 | into :getnext 41 | from dictionary.columns 42 | where libname='WORK' and memname='NODES' and upcase(name)='NODE' 43 | ; 44 | *----------------------------------------------------------------------; 45 | * Get next unassigned node ; 46 | *----------------------------------------------------------------------; 47 | &getnext; 48 | %do %while (&sqlobs and not &sqlrc) ; 49 | *----------------------------------------------------------------------; 50 | * Set subnet to next id ; 51 | *----------------------------------------------------------------------; 52 | %let subnetid=%eval(&subnetid+1); 53 | update nodes set subnet=&subnetid where node=&next; 54 | %do %while (&sqlobs) ; 55 | *----------------------------------------------------------------------; 56 | * Get list of connected nodes for this subnet ; 57 | *----------------------------------------------------------------------; 58 | create table new as 59 | select distinct a.&to as node 60 | from &in a, nodes b, nodes c 61 | where a.&from= b.node 62 | and a.&to= c.node 63 | and b.subnet = &subnetid 64 | and c.subnet = . 65 | ; 66 | %if "&directed" ne "1" %then %do; 67 | insert into new 68 | select distinct a.&from as node 69 | from &in a, nodes b, nodes c 70 | where a.&to= b.node 71 | and a.&from= c.node 72 | and b.subnet = &subnetid 73 | and c.subnet = . 74 | ; 75 | %end; 76 | *----------------------------------------------------------------------; 77 | * Update subnet for these nodes ; 78 | *----------------------------------------------------------------------; 79 | update nodes set subnet=&subnetid 80 | where node in (select node from new ) 81 | ; 82 | %end; 83 | *----------------------------------------------------------------------; 84 | * Get next unassigned node ; 85 | *----------------------------------------------------------------------; 86 | &getnext; 87 | %end; 88 | *----------------------------------------------------------------------; 89 | * Create output dataset by adding subnet number. ; 90 | *----------------------------------------------------------------------; 91 | create table &out as 92 | select distinct a.*,b.subnet as &subnet 93 | from &in a , nodes b 94 | where a.&from = b.node 95 | ; 96 | quit; 97 | %mend subnet ; 98 | -------------------------------------------------------------------------------- /substrn.sas: -------------------------------------------------------------------------------- 1 | %macro substrn 2 | /*---------------------------------------------------------------------- 3 | Subset string (simulation of SUBSTRN function) 4 | ----------------------------------------------------------------------*/ 5 | (string /* String to subset */ 6 | ,position /* Start position */ 7 | ,length /* Length of substring */ 8 | ); 9 | %local len s e; 10 | %*---------------------------------------------------------------------- 11 | Get length of string. Calculate start and end positions within string. 12 | When LENGTH is not specified use length of string. 13 | ----------------------------------------------------------------------; 14 | %let len=%length(&string); 15 | %if 0=%length(&length) %then %let length=&len; 16 | %let s=%sysfunc(max(&position,1)); 17 | %let e=%sysfunc(min(&len,&length+&position-1)); 18 | %*--------------------------------------------------------------------- 19 | Use SUBSTR to return the part of the string requested. 20 | ----------------------------------------------------------------------; 21 | %if (&s <= &len) and (&s <= &e) %then %substr(&string,&s,&e-&s+1); 22 | %mend substrn; 23 | -------------------------------------------------------------------------------- /symget.sas: -------------------------------------------------------------------------------- 1 | %macro symget 2 | /*---------------------------------------------------------------------- 3 | Get value for macro variable that is hidden by local macro variable 4 | ----------------------------------------------------------------------*/ 5 | (mvar /* Macro variable name */ 6 | ,include= /* Space delimited list of macro scopes to search */ 7 | ,exclude= /* Space delimited list of macro scopes to ignore */ 8 | ,recurse= /* Used internally by the macro */ 9 | ); 10 | /*---------------------------------------------------------------------- 11 | Use SASHELP.VMACRO to pull value for a macro variable that is hidden by 12 | local macro variables with the same name. 13 | ----------------------------------------------------------------------- 14 | $Calls to: -none- 15 | ----------------------------------------------------------------------- 16 | Usage: 17 | 18 | %* Allow GMV to serve as default value ; 19 | %if %bquote(&MVAR) = %then %let mvar=%symget(mvar); 20 | 21 | %* Allow both GMV and parameter style macro calls ; 22 | %macro aesumm(dsnin=,dsnut=,sev=); 23 | %local macro; 24 | %let macro=&sysmacroname; 25 | %if %bquote(&dsnin=) %then %let dsnin=%symget(dsnin,exclude=¯o); 26 | %if %bquote(&dsnut=) %then %let dsnut=%symget(dsnut,exclude=¯o); 27 | %if %bquote(&sev=) %then %let sev=%symget(sev,exclude=¯o); 28 | .... 29 | %mend aesumm; 30 | 31 | ------------------------------------------------------------------------ 32 | Notes: 33 | - Default to finding GLOBAL macro variable. 34 | - Set SYSRC=1 when macro variable not found. 35 | - Macro will call itself recursively to eliminate extra trailing blanks 36 | pulled from SASHELP.VMACRO. 37 | ----------------------------------------------------------------------- 38 | History: 39 | 40 | 2011/04/05 abernt Creation 41 | ----------------------------------------------------------------------*/ 42 | %local macro did rc where scope name value offset; 43 | %let macro=&sysmacroname; 44 | 45 | %if "&recurse"="0" %then %do; 46 | %*---------------------------------------------------------------------- 47 | Return raw VALUE including any trailing spaces from SASHELP.VMACRO as 48 | the result of the macro call. Stop when all observations are read or 49 | OFFSET=0 indicates that the start of another instance variable has begun. 50 | -----------------------------------------------------------------------; 51 | %let where=%upcase(name="&mvar" and scope="&include"); 52 | %let did=%sysfunc(open(sashelp.vmacro(where=(&where)))); 53 | %syscall set(did); 54 | %if 0=%sysfunc(fetch(&did)) %then %do; 55 | %do %until(%sysfunc(fetch(&did)) or 0=&offset);&value.%end; 56 | %end; 57 | %let rc=%sysfunc(close(&did)); 58 | %end; 59 | %else %if %bquote(&mvar)^= %then %do; 60 | %*---------------------------------------------------------------------- 61 | Use FINDW() to filter SCOPE. Include this macro in EXCLUDE list. 62 | Fetch first observation to get SCOPE and NAME. Call this macro 63 | recursively to retrive value into local macro variable. 64 | Return value of local macro variable. 65 | -----------------------------------------------------------------------; 66 | %if %length(&include) %then %let where=findw("&include",scope,'','ir'); 67 | %else %let where=not findw("¯o &exclude",scope,'','ir'); 68 | %let where=name=%upcase("&mvar") and &where; 69 | %let did=%sysfunc(open(sashelp.vmacro(where=(&where)))); 70 | %syscall set(did); 71 | %let rc=%sysfunc(fetch(&did)); 72 | %let did=%sysfunc(close(&did)); 73 | %if (0=&rc) %then %do; 74 | %let value=%¯o(&name,include=&scope,recurse=0); 75 | &value. 76 | %end; 77 | %end; 78 | 79 | %*---------------------------------------------------------------------- 80 | Set SYSRC=1 to indicate macro variable not found. 81 | -----------------------------------------------------------------------; 82 | %let sysrc=%eval(%bquote(&name)=); 83 | 84 | %mend symget; 85 | -------------------------------------------------------------------------------- /syslput612.sas: -------------------------------------------------------------------------------- 1 | %macro syslput612(macvar,macval,remote=); 2 | /*-------------------------------------------------------------- 3 | * 4 | * SAS TEST LIBRARY 5 | * 6 | * NAME: SYSLPUT 7 | * TITLE: MACRO FOR CREATING REMOTE MACRO VARIABLES 8 | * INPUT: 9 | * OUTPUT: 10 | * SPEC.REQ: 11 | * SYSTEMS: ALL 12 | * PRODUCT: SAS/CONNECT 13 | * KEYS: 14 | * REF: 15 | * COMMENTS: 16 | * SUPPORT: LANGSTON, B. 17 | * UPDATE: 01mar95. 18 | * 19 | *--------------------------------------------------------------*/ 20 | /****************************************************************/ 21 | /* SYSLPUT is the opposite of SYSRPUT. SYSLPUT creates a macro*/ 22 | /* variable in the remote environment. The user must specify */ 23 | /* the macro variable and its value. Optionally, the user */ 24 | /* may specify the remote session id; the default session is */ 25 | /* the current session. */ 26 | /****************************************************************/ 27 | options nosource nonotes; 28 | %let str=%str(rsubmit &remote;options nosource;) 29 | %nrstr(%let) %str(&macvar = &macval;options source;endrsubmit;); 30 | &str; options notes source; 31 | /*----------------------------------------------------------------* 32 | EXAMPLES: 33 | 34 | (1) Macro variable to current (default) remote session: 35 | 36 | %syslput(rc,&sysinfo) 37 | 38 | (2) Macro variable to specified remote session: 39 | 40 | %syslput(flag,1,remote=mvs) 41 | 42 | *----------------------------------------------------------------*/ 43 | %mend syslput612; 44 | -------------------------------------------------------------------------------- /tdexist.sas: -------------------------------------------------------------------------------- 1 | %macro tdexist 2 | /*---------------------------------------------------------------------------- 3 | Return type of a teradata table or view to test if it exists 4 | ----------------------------------------------------------------------------*/ 5 | (name /* Name of object to find (REQ) */ 6 | ,mvar=tdexist /* Name of macro variable to retrieve results. */ 7 | ,connection=td /* Name of existing PROC SQL database connection */ 8 | ); 9 | /*---------------------------------------------------------------------------- 10 | Return type of a teradata table or view to test if it exists 11 | 12 | If PROC SQL is running it will assume that the connection already exists and 13 | fail if it does not. Otherwise it will use %TDCONNECT(PROC_SQL) to start 14 | PROC SQL and make connection to Teradata as TD. Check that macro for 15 | information on how to create a Teradata connecton inside of PROC SQL. 16 | 17 | MVAR will be set to one of : 18 | VIEW 19 | TABLE 20 | VOLATILE TABLE 21 | NONE 22 | 23 | ------------------------------------------------------------------------------ 24 | Calls to: parmv.sas squote.sas tdconnect.sas 25 | ------------------------------------------------------------------------------ 26 | $Modification History 27 | 28 | ----------------------------------------------------------------------------*/ 29 | %local parmerr macro tddb userid table db result exists optsave ; 30 | %let macro=&sysmacroname; 31 | 32 | %*---------------------------------------------------------------------------- 33 | Set RESULT to NONE to indicate failure. 34 | -----------------------------------------------------------------------------; 35 | %let result=NONE; 36 | 37 | %*---------------------------------------------------------------------------- 38 | Check user parameters. 39 | -----------------------------------------------------------------------------; 40 | %parmv(name,_req=1) 41 | %parmv(connection,_def=td) 42 | %parmv(mvar,_def=¯o) 43 | %if (&parmerr) %then %goto quit; 44 | 45 | %*---------------------------------------------------------------------------- 46 | Check that MVAR is not one of the macro variables local to TDEXIST macro. 47 | -----------------------------------------------------------------------------; 48 | %if %sysfunc(indexw(PARMERR MACRO TDDB USERID TABLE DB RESULT EXISTS 49 | NAME MVAR CONNECTION,&mvar)) %then 50 | %parmv(mvar,_msg=Macro variable already in use by ¯o) 51 | ; 52 | %*---------------------------------------------------------------------------- 53 | Check that NAME has proper number of levels. 54 | -----------------------------------------------------------------------------; 55 | %if 1 < %sysfunc(countc(&name,.)) %then 56 | %parmv(NAME,_msg=Only one or two level names allowed) 57 | ; 58 | %if (&parmerr) %then %goto quit; 59 | 60 | %*---------------------------------------------------------------------------- 61 | Make sure the target macro variable exists. 62 | -----------------------------------------------------------------------------; 63 | %if %length(&mvar) %then %if not %symexist(&mvar) %then %global &mvar; 64 | 65 | %*---------------------------------------------------------------------------- 66 | If PROC SQL is not running then start it by calling %TDCONNECT. 67 | -----------------------------------------------------------------------------; 68 | %if not (&sysprocname=SQL) %then %do; 69 | %tdconnect(proc_sql); 70 | %if (&sysrc) %then %goto quit; 71 | %let connection=TD; 72 | %end; 73 | 74 | %*---------------------------------------------------------------------------- 75 | Turn off SASTRACE settings to reduce LOG messages. 76 | -----------------------------------------------------------------------------; 77 | %let optsave=%sysfunc(getoption(notes)); 78 | %let optsave=&optsave sastrace="%sysfunc(getoption(sastrace))"; 79 | options nonotes sastrace=none; 80 | 81 | %*---------------------------------------------------------------------------- 82 | Check if connection is working and get the current db and username . 83 | -----------------------------------------------------------------------------; 84 | select db,userid into :tddb trimmed,:userid trimmed 85 | from connection to &connection (select user as userid, database as db) 86 | ; 87 | %if (^&sqlobs) %then %do; 88 | %parmv(_msg=Unable to query Teradata); 89 | %goto quit; 90 | %end; 91 | 92 | %*---------------------------------------------------------------------------- 93 | Split NAME into DB and TABLE. 94 | -----------------------------------------------------------------------------; 95 | %let table=%sysfunc(dequote(%scan(&name,-1,.))); 96 | %if %index(&name,.) %then %let db=%sysfunc(dequote(%scan(&name,-2,.))); 97 | %else %let db=&tddb; 98 | 99 | *----------------------------------------------------------------------------; 100 | * Check if table or view exists ; 101 | *----------------------------------------------------------------------------; 102 | select obj into :result trimmed from connection to &connection 103 | (select case when (tablekind in ('T','O')) then 'TABLE' 104 | else 'VIEW' end as obj 105 | from dbc.tablesv 106 | where databasename = %squote(&db) and tablename= %squote(&table) 107 | and tablekind in ('V','T','O') 108 | ) 109 | ; 110 | %let exists=&sqlobs; 111 | 112 | %if (^&exists and &db=&userid) %then %do; 113 | *----------------------------------------------------------------------------; 114 | * Check for any Volatile tables ; 115 | *----------------------------------------------------------------------------; 116 | select 1 into :exists from connection to &connection (help volatile table) ; 117 | 118 | %if (&exists) %then %do; 119 | *----------------------------------------------------------------------------; 120 | * Check if this Volatile table exists ; 121 | *----------------------------------------------------------------------------; 122 | select 'VOLATILE TABLE' into :result 123 | from connection to &connection (help volatile table) 124 | %*---------------------------------------------------------------------------- 125 | Set VARNAME based on VALIDVARNAME setting. 126 | -----------------------------------------------------------------------------; 127 | %if %sysfunc(getoption(validvarname))=ANY %then 128 | where upcase('table name'n) = "&table" 129 | ;%else 130 | where upcase(table_name) = "&table" 131 | ; 132 | ; 133 | %let exists=&sqlobs; 134 | %end; 135 | %end; 136 | 137 | %quit: 138 | options &optsave; 139 | %let &mvar=&result; 140 | %mend tdexist; 141 | -------------------------------------------------------------------------------- /varexist.sas: -------------------------------------------------------------------------------- 1 | %macro varexist 2 | /*---------------------------------------------------------------------- 3 | Check for the existence of a specified variable. 4 | ----------------------------------------------------------------------*/ 5 | (ds /* Data set name */ 6 | ,var /* Variable name */ 7 | ,info /* LEN = length of variable */ 8 | /* FMT = format of variable */ 9 | /* INFMT = informat of variable */ 10 | /* LABEL = label of variable */ 11 | /* NAME = name in case as stored in dataset */ 12 | /* TYPE = type of variable (N or C) */ 13 | /* Default is to return the variable number */ 14 | ); 15 | 16 | /*---------------------------------------------------------------------- 17 | This code was developed by HOFFMAN CONSULTING as part of a FREEWARE 18 | macro tool set. Its use is restricted to current and former clients of 19 | HOFFMAN CONSULTING as well as other professional colleagues. Questions 20 | and suggestions may be sent to TRHoffman@sprynet.com. 21 | ----------------------------------------------------------------------- 22 | Usage: 23 | 24 | %if %varexist(&data,NAME) 25 | %then %put input data set contains variable NAME; 26 | 27 | %put Variable &column in &data has type %varexist(&data,&column,type); 28 | ------------------------------------------------------------------------ 29 | Notes: 30 | 31 | The macro calls resolves to 0 when either the data set does not exist 32 | or the variable is not in the specified data set. Invalid values for 33 | the INFO parameter returns a SAS ERROR message. 34 | ----------------------------------------------------------------------- 35 | History: 36 | 37 | 12DEC98 TRHoffman Creation 38 | 28NOV99 TRHoffman Added info parameter (thanks Paulette Staum). 39 | ----------------------------------------------------------------------*/ 40 | %local dsid rc varnum; 41 | 42 | %*---------------------------------------------------------------------- 43 | Use the SYSFUNC macro to execute the SCL OPEN, VARNUM, 44 | other variable information and CLOSE functions. 45 | -----------------------------------------------------------------------; 46 | %let dsid = %sysfunc(open(&ds)); 47 | 48 | %if (&dsid) %then %do; 49 | %let varnum = %sysfunc(varnum(&dsid,&var)); 50 | 51 | %if (&varnum) & %length(&info) %then 52 | %sysfunc(var&info(&dsid,&varnum)) 53 | ; 54 | %else 55 | &varnum 56 | ; 57 | 58 | %let rc = %sysfunc(close(&dsid)); 59 | %end; 60 | 61 | %else 0; 62 | 63 | %mend varexist; 64 | -------------------------------------------------------------------------------- /varinfo.sas: -------------------------------------------------------------------------------- 1 | %macro varinfo 2 | /*---------------------------------------------------------------------- 3 | Return information for a variable 4 | ----------------------------------------------------------------------*/ 5 | (ds /* Dataset name */ 6 | ,var /* Variable name */ 7 | ,info /* Type of information to return (default=EXIST) */ 8 | /* EXIST = Return 1 if the variable exists */ 9 | /* NUM = Variable number */ 10 | /* TYPE = Type of variable (N for numeric, C for character) */ 11 | /* LEN = Length of variable */ 12 | /* FMT = Format attached to variable */ 13 | /* INFMT = Informat attached to variable */ 14 | /* LABEL = Label attached to variable */ 15 | /* FMTCAT = Use FMTINFO() to find format category */ 16 | ) / minoperator mindelimiter=' '; 17 | 18 | /*---------------------------------------------------------------------- 19 | Based on the VAREXIST() macro created by Tom Hoffman in 1998/1999. 20 | 21 | Changes from the original VAREXIST() macro are: 22 | - When INFO is not specified the result is just 0 or 1, not varnum. 23 | - LABEL is returned as quoted with single quotes. 24 | - Addition of NUM, EXIST and FMTCAT information options. 25 | - Better parameter testing of INFO parameter. 26 | 27 | Usage examples: 28 | 29 | %if %varinfo(INDS,NAME) %then %put INDS contains variable NAME; 30 | %put Variable &column in &data has type %varinfo(&data,&column,type); 31 | %put Variable &NAME has format category %varinfo(&data,&name,cat); 32 | %put Variable &NAME has label %varinfo(&data,&name,label); 33 | ------------------------------------------------------------------------ 34 | Notes: 35 | 36 | The macro calls resolves to 0 when either the dataset does not exist 37 | or the variable is not in the specified dataset or an invalid INFO 38 | is requested. 39 | 40 | ------------------------------------------------------------------------ 41 | Test cases: 42 | 43 | %put |%varinfo(sashelp.stocks,date)|; %* |1| ; 44 | %put |%varinfo(sashelp.stocks,date,num)|; %* |2| ; 45 | %put |%varinfo(sashelp.stocks,date,type)|; %* |N| ; 46 | %put |%varinfo(sashelp.stocks,date,len)|; %* |8| ; 47 | %put |%varinfo(sashelp.stocks,date,fmt)|; %* |DATE.| ; 48 | %put |%varinfo(sashelp.stocks,date,fmtcat)|; %* |date| ; 49 | %put |%varinfo(sashelp.stocks,stock,type)|; %* |C| ; 50 | %put |%varinfo(sashelp.stocks,stock,len)|; %* |9| ; 51 | %put |%varinfo(sashelp.stocks,stock,fmt)|; %* || ; 52 | %put |%varinfo(sashelp.stocks,open,fmt)|; %* |DOLLARY8.2| ; 53 | %put |%varinfo(sashelp.stocks,open,fmtcat)|; %* |curr| ; 54 | %put |%varinfo(sashelp.stocks,open,infmt)|; %* |BEST32.| ; 55 | %put |%varinfo(sashelp.stocks,adjclose,label)|; %* |'Adjusted Close'| ; 56 | %put |%varinfo(sashelp.stocks,date,label)|; %* |' '| ; 57 | %put |%varinfo(sashelp.stocks,date,junk)|; %* |0| and error ; 58 | %put |%varinfo(sashelp.stocks,no_such_var)|; %* |0| ; 59 | %put |%varinfo(no_such_ds,var)|; %* |0| ; 60 | ----------------------------------------------------------------------- 61 | History: 62 | 63 | 2023-11-03 abernt Creation 64 | ----------------------------------------------------------------------*/ 65 | %local infos dsid varnum rc fmt return; 66 | %let infos=EXIST NUM TYPE LEN FMT INFMT LABEL FMTCAT; 67 | %let return=0; 68 | %if 0=%length(&info) %then %let info=EXIST; 69 | %else %if not (%qupcase(&info) in (&infos)) %then %do; 70 | %put ERROR: &sysmacroname: &info is not a valid value for the INFO 71 | parameter.; 72 | %put ERROR: &sysmacroname: Valid values are: &infos..; 73 | %goto quit; 74 | %end; 75 | %else %let info=%upcase(&info); 76 | 77 | %*---------------------------------------------------------------------- 78 | Open the dataset. Find the variable number. Then return requested info. 79 | -----------------------------------------------------------------------; 80 | %let dsid = %sysfunc(open(&ds)); 81 | %if (&dsid) %then %do; 82 | %let varnum = %sysfunc(varnum(&dsid,&var)); 83 | %if (&varnum) %then %do; 84 | %if (&info=EXIST) %then %let return=1; 85 | %else %if (&info=NUM) %then %let return=&varnum; 86 | %else %if (&info=LABEL) %then %do; 87 | %let return=%qsysfunc(varlabel(&dsid,&varnum)); 88 | %if 0=%length(&return) %then %let return=' '; 89 | %else %let return=%sysfunc(quote(&return,%str(%'))); 90 | %end; 91 | %else %if (&info=FMTCAT) %then %do; 92 | %let fmt=%sysfunc(varfmt(&dsid,&varnum)); 93 | %if %length(&fmt) %then %let fmt 94 | =%sysfunc(substrn(&fmt,1,%sysfunc(findc(&fmt,.,bsdk)))); 95 | %if %length(&fmt) %then %let return=%sysfunc(fmtinfo(&fmt,cat)); 96 | %else %if N=%sysfunc(vartype(&dsid,&varnum)) %then %let return=num; 97 | %else %let return=char; 98 | %end; 99 | %else %let return=%sysfunc(var&info(&dsid,&varnum)); 100 | %end; 101 | %let rc = %sysfunc(close(&dsid)); 102 | %end; 103 | 104 | %quit: 105 | &return. 106 | %mend varinfo; 107 | -------------------------------------------------------------------------------- /xpttype.sas: -------------------------------------------------------------------------------- 1 | %macro xpttype(filename /* fileref or quoted physical filename */); 2 | /*---------------------------------------------------------------------------- 3 | Check file to see what type of transport file it is. 4 | 5 | Returns 6 | CPORT for PROC CPORT/CIMPORT file 7 | XPORT for V5 export file 8 | XPORT_V9 for V8/9 export file 9 | SAS7BDAT for SAS dataset 10 | UNKNOWN for any other file 11 | 12 | Examples: 13 | %put %xpttype('~/xport.xpt'); 14 | %put %xpttype('~/subj.xpt'); 15 | %put %xpttype('~/r_lrevw.xpt'); 16 | %put %xpttype('~/test1.sas7bdat'); 17 | 18 | ----------------------------------------------------------------------------*/ 19 | %local return rc ; 20 | %*---------------------------------------------------------------------------- 21 | Set default value of UNKNOWN 22 | -----------------------------------------------------------------------------; 23 | %let return=UNKNOWN; 24 | 25 | %*---------------------------------------------------------------------------- 26 | Use %SYSFUNC() to call DOSUBL to run a data step to read the first 80 bytes 27 | of the file. 28 | -----------------------------------------------------------------------------; 29 | %let rc=%sysfunc(dosubl(%nrstr( 30 | data _null_; 31 | infile &filename recfm=f lrecl=80 obs=1; 32 | input; 33 | list; 34 | if _infile_= 35 | '**COMPRESSED** **COMPRESSED** **COMPRESSED** **COMPRESSED** **COMPRESSED********' 36 | then call symputx("return",'CPORT'); 37 | else if _infile_=:'HEADER RECORD*******LIB' 38 | and substr(_infile_,29)='HEADER RECORD!!!!!!!000000000000000000000000000000' then do; 39 | select (substr(_infile_,21,8)); 40 | when ('LIBRARY') call symputx("return",'XPORT'); 41 | when ('LIBV8') call symputx("return",'XPORT_V9'); 42 | otherwise; 43 | end; 44 | end; 45 | else if _infile_=:'000000000000000000000000c2ea8160b31411cfbd92080009c7318c181f1011'x 46 | then call symputx("return",'SAS7BDAT') 47 | ; 48 | run; 49 | ))); 50 | 51 | %*---------------------------------------------------------------------------- 52 | Return to value of &RETURN as the output of the macro. 53 | -----------------------------------------------------------------------------; 54 | &return. 55 | %mend xpttype ; 56 | --------------------------------------------------------------------------------