Some Useful SAS Macro Techniques - Part 2

November, 2008
by Phil Mason

Last month I covered some very useful macro programming techniques that I have used over my many years of SAS programming. This month I have three more techniques which are also very useful and often not widely known.

Creating Implicit & Command Macros

Implicit macros also have two other names: statement-style macros and name-style macros. To create one you have to do two things:

  1. Specify the implmac option, which turns implicit macros on. This is set off by default. If you don't turn this on then when you use the implicit macro you will get an error like this:

    ERROR 180-322: Statement is not valid or it is used out of proper order.

  2. Specify the stmt option on the %macro statement as you define your macro.

The following code demonstrates a simple implicit macro which prints a dataset. Notice that we turn implicit macros on first, then we define the macro with the stmt option following the slash. The most interesting thing about implicit macros is that when we use them we don't need a "%" in front of the macro name, nor do we need brackets around the parameters. We do need to end the macro call with a semi-colon though.

options implmac ; %macro p(dset) / stmt ; proc print data=&dset ; run ; %mend p ; p sashelp.class ;

When implicit macros are being used, SAS scans the first word of every statement to see if it is an implicit macro. One of my favorite applications of this in SAS 9.1.3 is to use implicit macros to redefine the title statement, so that when a title statement is used I can run my macro, which modifies the statement in some way. This no longer works in SAS 9.2 though.

Command macros allow you to define macros which can be used as commands, in the command box or on the command line in interactive SAS. There are many commands provided by SAS, but this feature allows you to create your own which either combine several existing commands or add new functionality. To create a command macro you must do 2 things:

  1. Turn on the cmdmac option, to enable command macros.
  2. Specify the cmd option on the %macro statement following a slash.

For example the following code demonstrates how we could create a new command called ls which would call the dir command to list the contents of a library and then tile all the windows within SAS.

options cmdmac ; %macro ls(lib) /cmd ; dir &lib ; tile ; %mend ls ;

You can do some much more useful things with command macros though by using the submit command. This allows you to submit SAS code to run from the command line. This example shows how we could make a command called cont which would run a proc contents to list the contents of a dataset for us.

%macro cont(dset) /cmd ; submit "proc contents data=&dset ; run ;" ; %mend cont ;

Debugging Macros Using Macro Options and Mfile

There are a range of macro options that can be set to help in the debugging of macro programs. The most important ones are as follows (in order of importance):

  1. Mprint - show SAS statements generated by macro program in log.
  2. Mlogic - write trace information concerning execution of macro to the log
  3. Symbolgen - shows results of resolving macro variables in log.
  4. Mautolocdisplay - when an autocall macro is invoked this will write a note to the log to tell you exactly where it came from (sometimes you have macros of the same name in different places and may be using the wrong one)
  5. Merror - write a note to the log when a macro program can't be found. This is on by default
  6. Mlogicnest - show macro nesting information with mlogic notes.
  7. Mprintnest - show macro nesting information with mprint notes.

But there is one other very useful option called mfile which sends all the text generated by mprint to an external file. This is incredibly useful when you have long and complicated macro programs and you can't tell where errors are occurring.

In the following macro program I produce a dataset with a suffix and do a simple calculation.

%macro test(i) ; data x&i ; set sashelp.class ; x=age/&i ; run ; %mend test ; options mprint ; %test(0) ;

However this will give me imprecise error messages which tell me the error is in line 1 column 1. This just points me to the macro program invocation rather than a line within the generated code.

NOTE: Division by zero detected at line 1 column 1. Name=Alfred Sex=M Age=14 Height=69 Weight=112.5 x=. _ERROR_=1 _N_=1

In a short, simple program it is easy enough to work out where the error is. However if your macro is producing thousands of lines it is of more help to find out precisely where the error is. By using the following options before I run the macro program I can send the generated code to a file.

options mprint mfile ; filename mprint 'c:\code.sas' ;

Then I can examine the code produced, or even run it.

109 data x0 ; 110 set sashelp.class ; 111 x=age/0 ; 112 run ; NOTE: Division by zero detected at line 111 column 6. Name=Alfred Sex=M Age=14 Height=69 Weight=112.5 x=. _ERROR_=1 _N_=1

When I run it I can now be directed to the exact line and column responsible for my error.

%sysexec For Calling Data Step Functions, Especially Data Step I/O Functions

The %sysexec macro function is one of the most useful features of the macro language. It allows you to run most of the SAS datastep functions directly from macro language. The syntax allows you to not only specify the function to run, but also to optionally specify a format to apply to the result that is returned from the function. The following SAS log demonstrates a common use of this.

Firstly we use %sysfunc with the date function to get todays date - which we might want to put into a title for a report. The date function returns the SAS date value, and it would be nice to format it for display.

113 %put %sysfunc(date()) ; 17849

By adding a format as the second parameter to %sysfunc we can format the date value.

115 %put %sysfunc(date(),date9.) ; 13NOV2008

The next example shows how I could use the pathname function to get the directories specified in a libref and use to define a fileref.

125 filename copy %sysfunc(pathname(sashelp)) ; 126 filename copy list ; NOTE: Fileref= COPY Physical Name= C:\Program Files\SAS\SASFoundation\9.2\nls\en\SASCFG Physical Name= C:\Program Files\SAS\SASFoundation\9.2\core\sashelp Physical Name= C:\Program Files\SAS\SASFoundation\9.2\af\sashelp Physical Name= C:\Program Files\SAS\SASFoundation\9.2\assist\sashelp Physical Name= C:\Program Files\SAS\SASFoundation\9.2\connect\sashelp Physical Name= C:\Program Files\SAS\SASFoundation\9.2\eis\sashelp Physical Name= C:\Program Files\SAS\SASFoundation\9.2\ets\sashelp Physical Name= C:\Program Files\SAS\SASFoundation\9.2\gis\sashelp Physical Name= C:\Program Files\SAS\SASFoundation\9.2\graph\sashelp Physical Name= C:\Program Files\SAS\SASFoundation\9.2\insight\sashelp Physical Name= C:\Program Files\SAS\SASFoundation\9.2\intrnet\sashelp Physical Name= C:\Program Files\SAS\SASFoundation\9.2\lab\sashelp Physical Name= C:\Program Files\SAS\SASFoundation\9.2\stat\sashelp

Finally one of my favorite and most often used examples involves using dataset I/O functions from macro language. These functions allow you to open a dataset, read information about it, get attributes and values of variables, etc. The following example gets the number of records in a dataset directly from macro language.

127 %let dsid=%sysfunc(open(sashelp.prdsale)) ; 128 %let nobs=%sysfunc(attrn(&dsid,nobs)) ; 129 %let dsid=%sysfunc(close(&dsid)) ; 130 %put The number of obs in sashelp.prdsale is &nobs ; The number of obs in sashelp.prdsale is 1440