I always struggle with the macro quoting.
Why does this work and the following modification does not???
%let kept=Arne Bert Hansi;
%macro quote_names(name_list);
%local i name;
%GLOBAL quoted_names;
%let quoted_names = ;
%let i = 1;
%do %while(%scan(&name_list, &i) ne );
%let name = %scan(&name_list, &i);
%let quoted_names = "ed_names %str(%')&name%str(%')%str(, );
%let i = %eval(&i + 1);
%end;
%let quoted_names = %substr("ed_names, 1, %length("ed_names) - 2);
%mend quote_names;
%quote_names(%str(&kept));
%put %STR("ed_names);
'Arne', 'Bert', 'Hansi'
But if I want to leave only the spaces without the comma, then it fails stating
ERROR: Literal contains unmatched quote.
ERROR: The macro QUOTE_NAMES will stop executing.
%let kept=Arne Bert Hansi;
%macro quote_names(name_list);
%local i name;
%GLOBAL quoted_names;
%let quoted_names = ;
%let i = 1;
%do %while(%scan(&name_list, &i) ne );
%let name = %scan(&name_list, &i);
%let quoted_names = "ed_names %str(%')&name%str(%')%str( );
%let i = %eval(&i + 1);
%end;
%let quoted_names = %substr("ed_names, 1, %length("ed_names) - 2);
%mend quote_names;
%quote_names(%str(&kept));
%put %STR("ed_names);
You might want to use the %QLIST macro, which is much easier than writing your own macro. I have used %QLIST for years and it works as desired.
Your particular problem can be solved by using %UNQUOTE
%let quoted_names = %unquote(%substr("ed_names, 1, %length("ed_names) - 2));
Why does %UNQUOTE work here? I have trouble explaining it other than to say when it looks like you have coded everything properly and you still get errors, and it usually involves a case where you created a quoted string in the macro, try %UNQUOTE. Or perhaps Mr. @Astounding can explain, you can search for his explanation of this in previous threads, or buy his book (the name of which I have forgotten) which also explains.
@PaigeMiller , thanks for the mention. Unfortunately the book "Macro Language Magic" is now out of print, and I have been retired and without SAS for more than 5 years now.
Off the top of my head, the most common "mysterious" need for %unquote is when macro language creates a list of quoted items for use in a WHERE clause within SQL code. Many times the software figures out to unquote a list, but not when SQL is involved.
Book still appears to be on sale at Amazon. https://www.amazon.com/SAS-Macro-Language-Magic-Discovering/dp/1612907105
Hello @acordes,
@acordes wrote:
Why does this work and the following modification does not???
This is because you left the final %LET statement in the macro, which had the purpose of deleting the unwanted %str(, ) after the last word, but which in the modified macro deletes the unwanted trailing blank plus the important closing quotation mark.
Solution: Delete that final %LET statement entirely and also the trailing %str( ) earlier in the code. (Note that you already get the separating blanks by inserting them before the newly appended name -- and the %LET statement automatically avoids creating a leading blank before the first name.)
do you need macro for the process?
how about data step?
%let kept=Arne Bert Hansi;
data _null_;
length str result $ 32767 word $ 128;
str=symget('kept');
sep = " "; /* space, but you can set whatever you like */
do i=1 by 1 while(NOT quit);
word = scan(str, i, " ");
if word NE " " then result = catX(sep, result, quote(strip(word)) );
else quit=1;
end;
call symputX("quoted_names", result, "G");
run;
%put "ed_names.;
Bart
O course DoSubL() function can help in wrapping it into a macro:
%let kept=Arne Bert Hansi;
%macro addQuotes(kept);
%local rc quoted_names;
%let rc=%sysfunc(dosubl(%str(
data _null_;
length str result $ 32767 word $ 128;
str=symget("kept");
sep = " "; /* space */
do i=1 by 1 while(NOT quit);
word = scan(str, i, " ");
if word NE " " then result = catX(sep, result, quote(strip(word)) );
else quit=1;
end;
call symputX("quoted_names", result, "L");
run;
)));
"ed_names.
%mend addQuotes;
%put %addQuotes(&kept.);
Bart
I find it usually works better to only add the separator before the next word.
Also an iterative DO loop are much,much easier to code and understand than a DO WHILE loop where you have to manually increment the counter.
%macro quote_names(name_list);
%local i name sep;
%GLOBAL quoted_names;
%let quoted_names = ;
%let sep=;
%do i=1 %to %sysfunc(countw(&name_list,%str( )));
%let name = %scan(&name_list, &i,%str( ));
%let quoted_names = "ed_names.&sep.%str(%')&name%str(%');
%let sep = %str(, );
%end;
%let quoted_names = %unquote("ed_names);
%mend quote_names;
Let's try it out.
14 %quote_names(a b); 15 %put |"ed_names|; |'a', 'b'| 16 %quote_names( ); 17 %put |"ed_names|; ||
Hey @acordes! I've got a macro and a function called cquote that was greatly inspired by @Tom's work and it has served me well over the years:
https://github.com/stu-code/sas/blob/master/utility-macros/cquote.sas
%macro cquote(strlist, quote);
%if(%upcase("e) = SINGLE) %then %let q = %str(%');
%else %let q = %str(%");
%unquote(%bquote(&q)%qsysfunc(tranwrd(%qsysfunc(compbl(%superq(strlist))),%bquote( ),%bquote(&q,&q)))%bquote(&q))
%mend;
proc fcmp outlib=work.funcs.str;
/* Double quote version */
function cquote(str$) $200;
return (cats('"', tranwrd(compbl(str),' ','","'), '"'));
endfunc;
/* Single quote version */
function scquote(str$) $200;
return (cats("'", tranwrd(compbl(str),' ',"','"), "'"));
endfunc;
run;
You can give these a try as well.
One more option just for fun, with FCMP function called in %SYSFUNC()
proc fcmp outlib=work.f.p;
function addQuotes(str $,s $, q) $ 32767;
length result $ 32767 sep word Usep $ 128 qt $ 1;
sep = s;
Usep = upcase(sep);
select;
when (Usep=" ") sep=" ";
when (Usep="C") sep=",";
when (Usep="SC") sep=";";
when (Usep="P") sep=".";
when (Usep=:"S") sep=substr(sep,2); /* string up to 127 bytes */
otherwise sep = " ";
end;
select(q);
when(2) qt='"';
when(1) qt="'";
otherwise qt=" ";
end;
quit=0;
do i = 1 by 1 while(NOT quit);
word = scan(str, i, " ");
if NOT missing(word) then
do;
if NOT (qt=" ") then word=quote(strip(word),qt);
result = catX(ifc(sep=" "," ",strip(sep)), result, strip(word));
end;
else quit=1;
end;
return(result);
endfunc;
quit;
options cmplib=work.f;
%let kept=Arne Bert Hansi;
%put %sysfunc(addQuotes(&kept.,C,2));
%put %sysfunc(addQuotes(&kept.,S!,0));
data _null_;
kept="Arne Bert Hansi";
str=addQuotes(kept,"C",2);
put kept= / str=;
str=addQuotes(kept,"SC",0);
put kept= / str=;
run;
Bart
Yet another approach: see Richard DeVenezia's %seplist() macro. It takes a list of items, and allows you to add quotes, or delimiters, or prefixes/suffixes, and more.
https://www.devenezia.com/downloads/sas/macros/index.php?m=seplist
You could also do this, for with and without commas, respectively:
data _null_;
call symputx("w_commas",tranwrd(quote(tranwrd("&kept",' ',quote(', '))),'""','"'));
call symputx("wo_commas",tranwrd(quote(tranwrd("&kept",' ',quote(' '))),'""','"'));
run;
%put &=w_commas;
%put &=wo_commas;
Results:
Multiple spaces can be a bit of a problem here:
%let kept=Arne Bert Hansi;
gives
46 data _null_;
47 call symputx("w_commas",tranwrd(quote(tranwrd("&kept",' ',quote(', '))),'""','"'));
48 call symputx("wo_commas",tranwrd(quote(tranwrd("&kept",' ',quote(' '))),'""','"'));
49 run;
NOTE: DATA statement used (Total process time):
real time 0.01 seconds
cpu time 0.01 seconds
50
51 %put &=w_commas;
W_COMMAS="Arne", "", "", "", "Bert", "", "Hansi"
52 %put &=wo_commas;
WO_COMMAS="Arne" "" "" "" "Bert" "" "Hansi"
Bart
or compbl() function
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.