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

2025 SAS Hackathon: There is still time!

Good news: We've extended SAS Hackathon registration until Sept. 12, so you still have time to be part of our biggest event yet – our five-year anniversary!

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.

SAS Training: Just a Click Away

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

Browse our catalog!

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