BookmarkSubscribeRSS Feed
Barite | Level 11

It is easy to find a macro where macro variables were not set explicitely as local leading to potential issues as you already know.


The readonly option of the %global statement could be used in the validation phase as a way to detect each time a global macro variable is updated.

This would be especially useful when including macro developed by other persons.

It would require a manual review as it is common to update an existing variable e.g %let mymacrovar=%upcase(&mymacrovar.);


But how do we get the list of all the global macro variables defined by the user in a dataset? Dictionary.macros lists all global macro variables included those of the SAS system. There is no user specific attribute in the dictionaries.


*temporary add on when checking for global macro variables;
proc sql;
    create table ref as
    select name
    from dictionary.macros
    where scope='GLOBAL'
      and name='TEST'; *would need to be able to select all the user defined macro var;

data _null_;
   set ref;
   call execute ('%' || 'global  / readonly ' || name || ';');

*Original code to be tested;
%let test=A;

%macro demo;
%let test=B;
%mend demo;


Tourmaline | Level 20

If I get it right, you don't want to prevent users for redefining a macro variable, but you wish to know when they do?

In your example, the assignment in the macro overrides the users global assignment, right?

I don't think that's something available OOTB.

Perhaps you can include logic in your "central" macros to check for this before the rest of the macro executes?

Data never sleeps

Unfortunately, SAS doesn't really have a solid concept of 'user-created' global macro variables vs  other types.  If you look at scope in the dictionary table, there is an AUTOMATIC scope which has lots of system-created global macro variables.  But lots of other macro variables that are created automatically be SAS (e.g. SQLOBS) end up in the GLOBAL scope.  And many of the clients create tons of macro variables that are also in the global scope.


Worse yet, there is a critical flaw (IMHO) which makes /readonly macro variables dangerous to use.  Even if the macro developer appropriately uses the %LOCAL statement to instantiate a local macro variable, if there is a GLOBAL READONLY macro variable with the same name, the macro will throw an error.  Which is ridiculous, because the whole point of the %LOCAL statement is to prevent the collision with the global symbol table.


%global /readonly test=A;

%macro demo();
  %local test ;
  %let test=B;
%mend demo;
Check out the Boston Area SAS Users Group (BASUG) video archives:
Super User Tom
Super User

The late great Tom Hoffman made a macro to help with this 25 years ago.  %chkmvars().

The main goal of the macro is to test if the macro variables exist (this was before the %SYMEXIST() function was created).  But the second example in the usage notes shows how to use it to help with this topic.


Basically you would add a call to this %chkmvars() at the end of your macro to see if during the execution of the macro you had created any LOCAL macro variable that was not in the list of macro variables you expected to create as LOCAL.   You would basically use this during the development and testing of the macro to make sure your %LOCAL statements were sufficient. 

Example from header:

%macro testit(data=);
%local abc def ghi;

... other code ...

%chkmvars(data abc def ghi,mode=testit)
%if %length(&nomatch) %then
  %put Local macro variables &nomatch are not defined.
%mend testit;

Full code:

%macro chkmvars
Checks list of macro variable against DICTIONARY.MACROS.

Returns either undefined variables or variables missing from a local
(list         /* List of variables to check */
,mode=MISS    /* MISS - returns variables in list but not in dictionary
                 macroname - returns local variables not in list */
,mvar=nomatch /* Returned macro variable */
,global=0     /* Global=1 automatically adds any missed variables to
                 the global environmemt */

This code was developed by HOFFMAN CONSULTING as part of a FREEWARE
macro tool set. Its use is restricted to current and former clients of
HOFFMAN CONSULTING as well as other professional colleagues. Questions
and suggestions may be sent to

1) check to see whether any of the macro variables ABC DEF or GHI have
   not been previosuly defined:

%local momatch;
%chkmvars(abc def ghi,mode=miss)
%if %length(&nomatch) %then
  %put Macro variables &nomatch are not defined.

2) check to see whether any local variables used in a macro are not
   used as parameters or listed in the %local statement:

%macro testit(data=);
%local abc def ghi;

... other code ...

%chkmvars(data abc def ghi,mode=testit)
%if %length(&nomatch) %then
  %put Local macro variables &nomatch are not defined.
%mend testit;

This tool is designed for macro developers. It serves two different
purposes depending upon the value of the MODE parameter:


Invoke macro CHKMVARS before PARMV in a macro that depends upon the
existence of macro variables that are not parameters and are not
globally defined in a macro that initializes the programming environment.
The developer may choose to either localize or globalize the missing
macro variables or to issue an error message and bail out.


As a final check to determine whether the %local statement is complete,
invoke CHKMVARS at the bottom of the macro. Remove the macro invocation
once the %local statement is complete.

12JUL99 TRHoffman Creation
%local macro parmerr n word save;
%let macro=chkmvars;

Validate macro parameters
%parmv(global,_req=1,_val=0 1)
%if (&parmerr) %then %goto quit;

%if ^%mvartest(&mvar) %then %do;
  %global &mvar;
%let &mvar =;

Use the SQL insert command to create a table with one row for each macro
variable in the list parameter.

Compare with DICTIONARY.MACROS and write nonmatched variables to &mvar.
%let save = %sysfunc(getoption(notes));
option nonotes;
proc sql noprint;
  create table _list_ (name char(8));
  insert into _list_
%do %until(&word =);
  %let n = %eval(&n + 1);
  %let word = %scan(&list,&n,%str( ));
  select name into:&mvar separated by ' '

%if (&mode = MISS) %then %do;
  from _list_
  where name not in (select name from dictionary.macros)

%else %do;
  from dictionary.macros
  where scope = "&mode" and
   name not in (select name from _list_)
  drop table _list_;
option &save;

%if (&global) & %length(&&&mvar) %then
  %global &&&mvar
; ;

%mend chkmvars;




Tom's answer reminds me of another approach to this problem. Frank DiIorio wrote a macro %WhatChanged() which is designed to detect side-effects of macros (e.g. changes to global macro variables, or changes to system options, or other global stuff).


You would use %WhatChanged() in a test script for a macro, like (untested):

%macro demo();
  %*oops forgot %local statement;
  %let test=B;
%mend demo;

%global A ; %whatChanged(action=start) %demo() %whatChanged(action=end)

The limitation of this approach for detecting macro variable collisions is that in the test script, you have to declare the names of all the macro variables that you want to check for potential collisions. So if you forgot to list a macro variable on your %local statement, you might also forget to list it in your test script. But since %WhatChanged also checks for other side-effects, I'm a fan of the approach.

Check out the Boston Area SAS Users Group (BASUG) video archives:



Registration is open! SAS is returning to Vegas for an AI and analytics experience like no other! Whether you're an executive, manager, end user or SAS partner, SAS Innovate is designed for everyone on your team. Register for just $495 by 12/31/2023.

If you are interested in speaking, there is still time to submit a session idea. More details are posted on the website. 

Register now!

How to Concatenate Values

Learn how use the CAT functions in SAS to join values from multiple variables into a single value.

Find more tutorials on the SAS Users YouTube channel.

Get the $99 certification deal.jpg



Back in the Classroom!

Select SAS Training centers are offering in-person courses. View upcoming courses for:

View all other training opportunities.

Discussion stats
  • 4 replies
  • 4 in conversation