SAS Code: Show the Values of All Macro Variables in Your SAS Session
SAS Business Intelligence applications use global macro variables to store information about the environment as well as prompt values entered by a user. When developing a Stored Process it is often helpful to review the values of these macro variables. A simple %PUT statement, %PUT _GLOBAL_ ; , will write a listing of the values of all global macro variables to the log, but the formatting leaves a lot to be desired. This post describes a simple utility macro, %PrintMacVars, which can be used to create a report listing macro variables and their values.
Background
It never hurts to spend free time reading SAS Users Group papers. My favorite site to find papers is www.lexjansen.com. Recently I came a across a paper by Frank DiIorio, Building the Better Macro: Best Practices for the Design of Reliable, Effective Tools. Frank has a lot of great papers, and tools, available at his website,codecraftersinc.com. Among a plethora of excellent recommendations in the paper, was a macro utility %PrintMacVars, which prints a simple report to the log, showing the values of all global macro variables. As I started playing with it, I decided it would be more useful if it had a SCOPE parameter to allow a user to report on local macro variables as wells as global variables, and also a parameter where the user could specify a list of macro variables to be printed. The version below is an extended version of Frank’s macro.
%PrintMacVars
%macro PrintMacVars
(scope=_local_ /*_local_ | _global_ | _user_ | _automatic_ | _ALL_ | macro name */
,mvar = /*OPTIONAL space-delimited list of macro vars to print. */
)
;
%local holdop ;
%let holdop=%sysfunc(getoption(ls,keyword)) ;
options ls=120;
%let scope=%upcase(&scope);
%*If scope is _LOCAL_ but %PrintMacVars was called in open code, set scope to _GLOBAL_;
%if &scope=_LOCAL_ and %sysmexecdepth=1 %then %let scope=_GLOBAL_;
proc sql noprint;
create table __macvars as
select
scope
,name
,offset
,value
from dictionary.macros
where (scope ne "PRINTMACVARS")
%if &scope=_LOCAL_ %then %do;
and (scope="%sysmexecname(%sysmexecdepth - 1)") /*name of macro that called %PrintMacVars*/
%end;
%else %if &scope=_GLOBAL_ %then %do;
and (scope="GLOBAL")
%end;
%else %if &scope=_USER_ %then %do;
and (scope ne "AUTOMATIC")
%end;
%else %if &scope=_AUTOMATIC_ %then %do;
and (scope = "AUTOMATIC")
%end;
%else %if &scope=_ALL_ %then ;
%else %do;
and (scope="&scope") /*user passed a macro name as scope*/
%end;
%if %superq(mvar) ne %str() %then %do;
and (findw(%upcase("&mvar"),trim(name)))
%end;
order by scope, name, offset
;
quit;
data _null_;
if _n_ = 1 then
putlog 117*'='
/'%PrintMacVars listing of macro variables'
/ ' Called by: '
"%sysmexecname(%sysmexecdepth - 1)"
/ " Scope: &scope"
%if %superq(mvar) ne %str() %then %do;
/ " Macro vars specified: &mvar"
%end;
/
/ 'Macro Variable' @35 'First 50 Characters' @86 'Scope'
/ 33*'=' +1 50*'=' +1 32*'='
;
if eof then put 117*'=' ;
set __macvars end=eof;
where (offset=0);
putlog '&' name $33. value $char50. +1 scope $33. ;
run;
*cleanup;
options &holdop;
proc datasets library=work memtype=data nolist;
delete __macvars;
quit;
%mexit:
%mend PrintMacVars;
The macro has only two parameters. &SCOPE allows the user to specify the scope to be reported (_local_, _global_, _user_, _automatic_, _ALL_, or the name of a macro). The default is to report on all macro variables in the current scope (so if %PrintMacVars is called outside of a macro, it reports on global macro variables, if it is called within a macro, it reports on local macro variables). &MVAR allows the user to specify a list of macro variables to be reported.
The first step in the macro uses PROC SQL to read records from a dictionary table, DICTIONARY.MACROS, to generate a dataset that has the name of each macro variable, as well as its value and its scope. If you are not familiar with dictionary tables, they are automatically created by SAS, and they contain a wealth of metadata useful to SAS programmers (data about data, system options, titles, formats, etc.). Many users’ group papers have been written about dictionary tables, and Frank has an excellent dictionary table reference card available on his website. DICTIONARY.MACROS holds the values of all macro variables, so it is used as the source data for this utility. The WHERE clause on the SQL query subsets the data to select the macro variables of interest, according to the values the user specified for &SCOPE and &MVAR.
The second step is an old-fashioned DATA _NULL_ step, which reads in the data set generated by the SQL query, and uses PUTLOG statements to write the report to the log. The report lists each macro variable selected by the query, as well as its scope, and the first 50 characters of the value.
Using %PrintMacVars in a Stored Process
I often use PROC PRINTTO to write the log from a Stored Process to a permanent file, and find it helpful to have information on global macro variables recorded in the log. If a user emails me on Friday to say that they received an error from a Stored Process report they ran on Wednesday, I can open the log file, and the report on macro variables will tell me what client application they used to run the Stored Process, what ODS options were in effect (&_odsdest, &_odsstyle, etc.), and what prompt values the user provided. This information is critical to replicating the problem and debugging it.
Suppose I create a simple stored process, MakeReport, which has two prompts defined: DATA (name of source data set) and TITLE (title for report). If I start my stored process with a call to %PrintMacVars(), it will write the values of all macro variables to the log (both those entered by the user as values for prompts, and those created by the client application). So my Stored Process looks like:
When I run the stored process, I specify Data=sashelp.shoes, and Title=Printout of SASHELP.shoes. The log shows the report generated by %PrintMacVars:
47 +*write values of all global macro variables to the log; 48 +%PrintMacVars() ==================================================================================================================== %PrintMacVars listing of macro variables Called by: OPEN CODE Scope: _GLOBAL_ Macro Variable First 50 Characters Scope ================================= ================================================== ================================ &DATA sashelp.shoes GLOBAL &SYS_SQL_IP_ALL -1 GLOBAL &SYS_SQL_IP_STMT GLOBAL &TITLE Printout of SASHELP.shoes GLOBAL &_CLIENT StoredProcessService 9.3 JVM 1.6.0_27 Linux (am GLOBAL &_ENCODING GLOBAL &_GOPTIONS GLOBAL &_GOPT_DEVICE javaimg GLOBAL &_GOPT_HSIZE GLOBAL &_GOPT_VSIZE GLOBAL &_GOPT_XPIXELS GLOBAL &_GOPT_YPIXELS GLOBAL &_GRAFLOC /sasweb/graph GLOBAL &_HTCOOK JSESSIONID=5B4A9FBAF73A9274ED812DF7D1A603B4 GLOBAL &_HTUA Mozilla/4.0 (compatible MSIE 7.0 Windows NT 5.1 GLOBAL &_METAFOLDER /User Folders/Quentin McMullen/ GLOBAL &_METAPERSON Quentin McMullen GLOBAL &_METAUSER XXXXXXXX GLOBAL &_NAMEVALUE GLOBAL &_ODSDEST HTML GLOBAL &_ODSDOC GLOBAL &_ODSOPTIONS GLOBAL &_ODSSTYLE GLOBAL &_ODSSTYLESHEET GLOBAL &_PRINTOPTIONS GLOBAL &_PROGRAM /User Folders/Quentin McMullen/MakeReport GLOBAL &_REPLAY "&_URL?_sessionid=624C4000-0132-11E2-8001-35363238 GLOBAL &_REQMETH GET GLOBAL &_RESULT STREAM GLOBAL &_RMTADDR ###.###.###.# GLOBAL &_RMTHOST ###.###.###.# GLOBAL &_RR_CACHE GLOBAL &_RR_DOMAIN GLOBAL &_RR_FILEREF GLOBAL &_RR_PASSWORD GLOBAL &_RR_URL GLOBAL &_RR_USER GLOBAL &_SECUREUSERNAME XXXXXXXX GLOBAL &_SRVNAME XXXXXXXX GLOBAL &_SRVPORT 8080 GLOBAL &_STPERROR 0 GLOBAL &_TMPCAT APSWORK.TCAT08AB GLOBAL &_URL /SASStoredProcess/do GLOBAL &_USERLOCALE en_US GLOBAL &_USERNAME Quentin McMullen GLOBAL &_VERSION Version 9.3 (Build 473) GLOBAL =====================================================================================================================
The first thing I notice is that the Stored Process Web Application creates a LOT of global macro variables. Many of these can be useful to the Stored Process developer. I often use &_odsdest, &_username, and &_program in my code. Near the top of the list you can see the two stored process prompts, DATA and TITLE, and the values they were given when the stored process was called. If you would rather limit the list of macro variables to be printed, you can specify the list when you call %PrintMacVars, e.g. : %PrintMacVars(mvar=_username _program data title).
Using %PrintMacVars in a Macro
When developing and debugging a macro, it is often helpful to see the values of macro variables defined in the macro. This is particularly true when one macro calls another macro, and passes values to it through macro parameters.
Consider a simple macro which generates a printout of a few records from a dataset, and has a &DEBUG parameter. When the macro is called with &DEBUG=1 (on) it calls %PrintMacVars to write all of the local macro variables to the log.
%macro tprint
(data=
,obs=10
,title=
,debug=0
);
%let title=%sysfunc(propcase(&title));
%*If in debug mode, dump local macro vars to log;
%if &debug=1 %then %do;
%printMacVars()
%end;
title2 "&title";
proc print data=&data (obs=&obs);
run;
title2;
%mend tprint;
%tprint(data=sashelp.shoes
,title=printout of SASHELP.shoes
,obs=3
,debug=1
)
The log shows a report of the parametet values passed by the user:
96 %tprint(data=sashelp.shoes 97 ,title=printout of SASHELP.shoes 98 ,obs=3 99 ,debug=1 100 ) ===================================================================================================================== %PrintMacVars listing of macro variables Called by: %TPRINT Scope: _LOCAL_ Macro Variable First 50 Characters Scope ================================= ================================================== ================================ &DATA sashelp.shoes TPRINT &DEBUG 1 TPRINT &OBS 3 TPRINT &TITLE Printout Of Sashelp.Shoes TPRINT =====================================================================================================================
With a simple macro like %TPRINT, debugging could be done without %PRINTMACVARS. But with a complex macro design, where macros have several parameters, and values are passed between macros, and new macro variables are generated, often the first step of debugging is to check the values of macro variables at key points in the code. Adding calls to %PRINTMACVARS throughout the macro code can help dramatically.
Conclusion
One of the things I like most about the macro language is the ability to write little utility macros, like %PrintMacVars, and store them in an autocall library, so they are always there for you when you need them. Like many utility macros, %PrintMacVars does not provide you with a new functionality. You could always inspect the values of macro variables with %PUT statements, or with the SYMBOLGEN option. But %PrintMacVars makes it easy to generate a basic report that is easy to read. And once you have an easy way to print macro variables, you will find you do it more often, and find debugging macro code and stored processes much easier.
Do you have a favorite macro utility in your tool box? Drop some code, or a link, in the comments. As the saying goes, “Good programmers borrow from other programmers, great programmers steal.”
Tags: macros, stored process tips
















