When I use the following in a macro, I get an ERROR: message:
3 %let list1 = a b ;
4 %let list2 = b c ;
5
6 %let list = "%sysfunc( prxchange( s/\s+/%str(" , ")/ , -1 , %nrbquote(&list1. &list2.)))" ;
7 %put &list. ;
"a" , "b" , "b" , "c"
8
9
10
11 %macro tmp ;
12
13 data __dedup ;
14 do var = "%sysfunc( prxchange( s/\s+/%str(" , ")/ , -1 , %nrbquote(&list1.
14 ! &list2.)))" ;
ERROR: Expected close parenthesis after macro function invocation not found.
15 if var ne " " then output ;
16 end ;
17 run ;
18 %mend ;
19
20
21 %tmp ;
ERROR: Expected close parenthesis after macro function invocation not found.
NOTE: The SAS System stopped processing this step because of errors.
WARNING: The data set WORK.__DEDUP may be incomplete. When this step was stopped there were 0
observations and 1 variables.
NOTE: DATA statement used (Total process time):
real time 0.01 seconds
cpu time 0.01 seconds
I would appreciate any insight, references, or solutions. Note that I remove single and double quotation marks in a prior step. We are using "traditional" SAS variable names only, but that will make an interesting challenge to address.
Thank you,
Kevin
The error message is pretty straight forward. It is from SYSFUNC saying it did not see the double right parentheses you normally need in a expression like
%sysfunc(sasfunc())
So why does it work in open code and fail in macro code?
Let's look at your %sysfunc() call:
"%sysfunc( prxchange( s/\s+/%str(" , ")/ , -1 , %nrbquote(&list1. &list2.)))"
The issue is that inside a macro the parsing is getting confused by have the quotes macro quoted. So just remove those. You only need to quote the comma.
"%sysfunc( prxchange( s/\s+/" %str(,) "/ , -1 , %nrbquote(&list1. &list2.)))" ;
If you do want to use %STR() to macro quote the quotes then add % in front of each.
"%sysfunc( prxchange( s/\s+/%str(%" , %")/ , -1 , %nrbquote(&list1. &list2.)))"
Or use BQUOTE(). Also I am not sure why you are using NRBQUOTE() in the other place.
"%sysfunc( prxchange( s/\s+/%bquote(" , ")/ , -1 , %bquote(&list1. &list2.)))"
If you want to keep the issue in macro code then just make some deduping logic of your own.
%macro dedupvars(varlist);
%local newlist i newvar;
%do i=1 %to %sysfunc(countw(&varlist,%str( )));
%let newvar=%scan(&varlist,&i,%str( ));
%if not %sysfunc(indexw(%upcase(&newlist),%upcase(&newvar))) %then
%let newlist=&newlist &newvar
;
%end;
&newlist.
%mend dedupvars;
%put %dedupvars(A b c a B C e);
What I strongly suspect is happening is that the DO will accept character values in a list:
data example; do var= "A","B","X"; output; end; run;
So your
do var = "%sysfunc( prxchange( s/\s+/%str(" , ")/ , -1 , %nrbquote(&list1.
14 ! &list2.)))" ;
is two pieces of text:
"%sysfunc( prxchange( s/\s+/%str("
and
")/ , -1 , %nrbquote(&list1.&list2.)))"
So the 'functions' aren't closed /opened properly.
I can't tell what you are attempting to provide an alternate but question the need for the %str, or at least why it is there.
Thank you for the response.
Consider this:
4229 %let list1 = a b ;
4230 %let list2 = b c ;
4231
4232 data __dedup ;
4233 do var = "%sysfunc( prxchange( s/\s+/%str(" , ")/ , -1 , %nrbquote(&list1. &list2.)))" ;
4234 if var ne " " then put var= ;
4235 end ;
4236 run ;
var=a
var=b
var=b
var=c
NOTE: The data set WORK.__DEDUP has 1 observations and 1 variables.
NOTE: DATA statement used (Total process time):
real time 0.01 seconds
cpu time 0.00 seconds
I am taking a list of SAS variables and quoting them and placing commas between them. A B will be "A" , "B". I already removed possible quotation marks (single and double) and commas. I need to mask the comma in the regex, so I used the %STR() function. I am protecting against a user submitting the same variable in both lists, so I de-dup it. I figured the SQL (or SORT) procedure has a trusted way, so I needed to put the variables as values of a variable in a SAS data set. This seemed like an obvious approach. This must be an issue with the macro compiler, since it works fine above.
If I were to use SAS code and not macro, then the quotation mark would "protect" the comma:
4344 data __dedup ;
4345 var = prxchange( 's/\s+/" , "/'
4346 , -1
4347 , "A B"
4348 ) ;
4349 put var= ;
4350 run ;
var=A" , "B
NOTE: The data set WORK.__DEDUP has 1 observations and 1 variables.
NOTE: DATA statement used (Total process time):
real time 0.01 seconds
cpu time 0.00 seconds
This, also, was perplexing (at least from my level of understanding):
4250 %let list1 = %sysfunc( prxchange( s/\s+/%str(" , ")/ , -1 , %nrbquote(&list1.))) ;
4251 %put &list1. ;
a" , "b
4252
4253 %let list2 = %sysfunc( prxchange( s/\s+/%str(" , ")/ , -1 , %nrbquote(&list2.))) ;
NOTE: Line generated by the macro function "SYSFUNC".
1 b" , "c
-----
49
NOTE 49-169: The meaning of an identifier after a quoted string might change in a future SAS release. Inserting white space
between a quoted string and the succeeding identifier is recommended.
4254 %put &list2. ;
NOTE: Line generated by the macro variable "LIST2".
1 b" , "c
-----
49
b" , "c
NOTE 49-169: The meaning of an identifier after a quoted string might change in a future SAS release. Inserting white space
between a quoted string and the succeeding identifier is recommended.
Specifically, white space follows the double quotation mark; I purposefully inserted it. Quizzically, the first statement does not generate the code.
Kind regards,
Kevin
The error message is pretty straight forward. It is from SYSFUNC saying it did not see the double right parentheses you normally need in a expression like
%sysfunc(sasfunc())
So why does it work in open code and fail in macro code?
Let's look at your %sysfunc() call:
"%sysfunc( prxchange( s/\s+/%str(" , ")/ , -1 , %nrbquote(&list1. &list2.)))"
The issue is that inside a macro the parsing is getting confused by have the quotes macro quoted. So just remove those. You only need to quote the comma.
"%sysfunc( prxchange( s/\s+/" %str(,) "/ , -1 , %nrbquote(&list1. &list2.)))" ;
If you do want to use %STR() to macro quote the quotes then add % in front of each.
"%sysfunc( prxchange( s/\s+/%str(%" , %")/ , -1 , %nrbquote(&list1. &list2.)))"
Or use BQUOTE(). Also I am not sure why you are using NRBQUOTE() in the other place.
"%sysfunc( prxchange( s/\s+/%bquote(" , ")/ , -1 , %bquote(&list1. &list2.)))"
If you want to keep the issue in macro code then just make some deduping logic of your own.
%macro dedupvars(varlist);
%local newlist i newvar;
%do i=1 %to %sysfunc(countw(&varlist,%str( )));
%let newvar=%scan(&varlist,&i,%str( ));
%if not %sysfunc(indexw(%upcase(&newlist),%upcase(&newvar))) %then
%let newlist=&newlist &newvar
;
%end;
&newlist.
%mend dedupvars;
%put %dedupvars(A b c a B C e);
Tom,
As usual, an excellent reply with plenty of educational points. With more than 20 years of doing it, being wrong on the internet about SAS still stings 🙂
I violated one of my rules: mask nothing more than required.
I think that I used %NRBQUOTE() out of habit of quoting strings that might have, by bad practice, certain tokens, like % or &, even if the values might not be valid (I might have a error check for well-formed values at the beginning of a macro, but I carry it through). Also, I read Ian Whitlock's quoting papers (and posts) periodically, but I should probably read them again.
I like the elegance of your wholly macro approach to dedup the list.
Thank you,
Kevin
Registration is now open for SAS Innovate 2025 , our biggest and most exciting global event of the year! Join us in Orlando, FL, May 6-9.
Sign up by Dec. 31 to get the 2024 rate of just $495.
Register now!
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.
Ready to level-up your skills? Choose your own adventure.