BookmarkSubscribeRSS Feed
acordes
Rhodochrosite | Level 12

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 = &quoted_names %str(%')&name%str(%')%str(, );
        %let i = %eval(&i + 1);
    %end;
    %let quoted_names = %substr(&quoted_names, 1, %length(&quoted_names) - 2);

%mend quote_names;

%quote_names(%str(&kept));

%put %STR(&quoted_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 = &quoted_names %str(%')&name%str(%')%str( );
        %let i = %eval(&i + 1);
    %end;
    %let quoted_names = %substr(&quoted_names, 1, %length(&quoted_names) - 2);

%mend quote_names;

%quote_names(%str(&kept));

%put %STR(&quoted_names);

 

 

15 REPLIES 15
PaigeMiller
Diamond | Level 26

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(&quoted_names, 1, %length(&quoted_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.

--
Paige Miller
Astounding
PROC Star

@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.  

PaigeMiller
Diamond | Level 26

Book still appears to be on sale at Amazon. https://www.amazon.com/SAS-Macro-Language-Magic-Discovering/dp/1612907105

--
Paige Miller
FreelanceReinh
Jade | Level 19

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.)

yabwon
Amethyst | Level 16

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 &quoted_names.;

 

Bart

_______________
Polish SAS Users Group: www.polsug.com and communities.sas.com/polsug

"SAS Packages: the way to share" at SGF2020 Proceedings (the latest version), GitHub Repository, and YouTube Video.
Hands-on-Workshop: "Share your code with SAS Packages"
"My First SAS Package: A How-To" at SGF2021 Proceedings

SAS Ballot Ideas: one: SPF in SAS, two, and three
SAS Documentation



yabwon
Amethyst | Level 16

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;
)));
&quoted_names.
%mend addQuotes;

%put %addQuotes(&kept.);

Bart

_______________
Polish SAS Users Group: www.polsug.com and communities.sas.com/polsug

"SAS Packages: the way to share" at SGF2020 Proceedings (the latest version), GitHub Repository, and YouTube Video.
Hands-on-Workshop: "Share your code with SAS Packages"
"My First SAS Package: A How-To" at SGF2021 Proceedings

SAS Ballot Ideas: one: SPF in SAS, two, and three
SAS Documentation



Tom
Super User Tom
Super User

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 = &quoted_names.&sep.%str(%')&name%str(%');
    %let sep = %str(, );
  %end;
  %let quoted_names = %unquote(&quoted_names);
%mend quote_names;

Let's try it out.

14   %quote_names(a b);
15   %put |&quoted_names|;
|'a', 'b'|
16   %quote_names( );
17   %put |&quoted_names|;
||

 

Stu_SAS
SAS Employee

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(&quote) = 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.

yabwon
Amethyst | Level 16

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

_______________
Polish SAS Users Group: www.polsug.com and communities.sas.com/polsug

"SAS Packages: the way to share" at SGF2020 Proceedings (the latest version), GitHub Repository, and YouTube Video.
Hands-on-Workshop: "Share your code with SAS Packages"
"My First SAS Package: A How-To" at SGF2021 Proceedings

SAS Ballot Ideas: one: SPF in SAS, two, and three
SAS Documentation



Quentin
Super User

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

 

quickbluefish
Barite | Level 11

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:

W_COMMAS="Arne", "Bert", "Hansi"
WO_COMMAS="Arne" "Bert" "Hansi"
yabwon
Amethyst | Level 16

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

_______________
Polish SAS Users Group: www.polsug.com and communities.sas.com/polsug

"SAS Packages: the way to share" at SGF2020 Proceedings (the latest version), GitHub Repository, and YouTube Video.
Hands-on-Workshop: "Share your code with SAS Packages"
"My First SAS Package: A How-To" at SGF2021 Proceedings

SAS Ballot Ideas: one: SPF in SAS, two, and three
SAS Documentation



quickbluefish
Barite | Level 11
Sure, so just wrap &kept in %cmpres( ) within the syntax above.
yabwon
Amethyst | Level 16

or compbl() function

_______________
Polish SAS Users Group: www.polsug.com and communities.sas.com/polsug

"SAS Packages: the way to share" at SGF2020 Proceedings (the latest version), GitHub Repository, and YouTube Video.
Hands-on-Workshop: "Share your code with SAS Packages"
"My First SAS Package: A How-To" at SGF2021 Proceedings

SAS Ballot Ideas: one: SPF in SAS, two, and three
SAS Documentation



hackathon24-white-horiz.png

The 2025 SAS Hackathon has begun!

It's finally time to hack! Remember to visit the SAS Hacker's Hub regularly for news and updates.

Latest Updates

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.

SAS Training: Just a Click Away

 Ready to level-up your skills? Choose your own adventure.

Browse our catalog!

Discussion stats
  • 15 replies
  • 2313 views
  • 8 likes
  • 10 in conversation