全部版块 我的主页
论坛 数据科学与人工智能 数据分析与数据科学 SAS专版
19826 10
2014-07-01
楼 1 of 2

写在之前:
1.      写关于macro quoting的总结是需要一点勇气的。
2.      这个所谓的总结远远没有涵盖macro quoting所涉及到的全部问题,这篇文章的目的是让理解变得容易一点点。
3.      文章中用到的例子,有的是借用了其他文章中的想法(比如Ian Whitlock 的A Serious Look at Macro Quoting)。有的例子并不会经常用到,这里只是为了尽量清楚地解释某个问题。
4.      如果各位发现文中有任何不正确的地方,或是有更精确的解释,还望不吝赐教。

Why Quoting?

在SAS语言 中,比如data步中,ABC表示一个变量的名字,而“ABC”就是一个长度为3的字符串。SAS通过引号对字符串和变量名进行区分,这可以说是SAS语言里的quoting。

而在宏语言中,同样会遇到这种情况。比如逗号","既可以用来分隔某个宏调用或宏函数的参数(%SCAN(argument,n<, delimiters>)),也可以作为某个宏变量的值中一个普通的字符(%let value=part1,part2;)。在宏语言里,为了让宏处理器将某个逗号看作一个普通的字符,就需要用某种方法把逗号作为分隔符的功能隐藏起来(%put %scan(%str(part1,part2), 2, %str(,));),否则就会提示“ERROR:Macro function %SCAN has too many arguments. The excess arguments will be ignored”。这种方式就是宏语言中的quoting。

Quoting-Related Functions

  • %STR & %NRSTR
众所周之,这两个宏函数在编译阶段起作用。意思就是说,%STR的工作都是在宏处理器对宏进行编译过程中完成,而到了执行的时候,已经“看不到”%STR/%NRSTR了,只能看到他们的“工作成果”。这两个函数都是对参数中的特殊字符或运算符进行quote。如果参数中包含宏变量或宏的调用,那么在%STR/%NRSTR工作时是不执行这些调用的。

除这两个函数之外的Quoting函数都是在宏或宏语句执行阶段起作用。

  • %BQUOTE & %NRBQUOTE
这两个函的作用对象是“宏处理器对参数的解析结果”。也就是,如果参数中包含对宏变量或宏的调用,那宏处理器首先会执行这些调用,再将%BQUOTE/%NRBQUOTE作用于最后的结果。

宏语言中的另外两个quote函数%QUOTE和%NRQUOTE的功能都包含在%BQUOTE和%NRBQUOTE之中。

  • %SUPERQ
这个函数的参数是宏变量的名字,作用对象是该宏变量的值。如果宏变量的值里包含有对宏变量或宏的调用,宏处理器也不会去尝试执行这些调用。

  • Other functions/macros
宏语言中有一些以”Q”开头的函数或宏也可以实现对其运算结果的quote。
如函数 %QSCAN, %SUBSTR, %QSYSFUNC, %QUPCASE, 以及宏%QCOMPRES, %QLEFT, %QLOWCASE, %QTRIM。

  • %UNQUOTE
这个函数的作用在于解除对特殊字符或运算符的隐藏,恢复其原有的功能。

What to Quote

  • Macro quote函数都可以用来隐藏下面这些字符的功能:blank    =   NE  ;   | LE¬  + #  LT  ^  --  AND  GE  ~  *  OR  GT  , (comma) /  NOT    < IN   >  EQ   ;
  • %NRSTR, %NRQUOTE, %NRBQUOTE 和%SUPERQ还能隐藏&和%;
  • 对于不成对出现的引号和括号,
    %STR、%NRSTR、 %QUOTE  和%NRQUOTE 需要在引号或括号之前加‘%’;
    %BQUOTE、%NRBQUOTE不需要前置的‘%’;
  • 当字符串中有连续的百分号‘%’,并且百分号后面不是字母或下划线时,
    %BQUOTE和%NRBQUOTE会直接进行quote;
    %STR、%NRSTR、 %QUOTE  和%NRQUOTE会将两个连续的%处理成单个%。例如:
  %put %str(% %% %%% );
  %put %nrstr(% %% %%% %%%%);

  %put %quote(% %% %%% %%);
  %put %nrquote(% %% %%% );

  %put %bquote(% %% %%%);
  %put %nrbquote(% %% %%%%);
注:例子中的前四个%PUT语句中,quote函数的右括号之前连续的%如果是奇数个,那么在右括号之前需要一个空格,否则紧跟的右括号也会被做为字符串的一部分,这样就需要再加一个’)’来作为函数的结尾:%put %str(% %% %%%));

How to Quote

当宏处理器将quote函数作用在某个字符串上的时候,它会对其中的特殊的字符和运算符进行处理(具体来说应该是将其替换为其他的字符),并且也会在这个字符串的前后各加上一个十六进制字符,前边加的字符标志着字符串开始并记录quote函数的类型,后面加的字符标志字符串的结束。还有一点,quote函数作用时是会保留字符串前后的空格的。下面的几种方法可以显示出macro quoting的某些效果:(下面的例子是在SAS 9.1下运行,不同的SAS版本log里的显示可能不一样。)

1. 用%PUT语句在log中显示某个macro symbol table.
  %macro test;
       %local mvar1 mvar2 mvar3 mvar4 mvar5 mvar6 mvar7 mvar8;

       %let mvar1=a,b,c;
       %let mvar2=%str( a,b,c );
       %let mvar3=%str( &mvar1 );
       %let mvar4=%unquote( &mvar2 );
       %let mvar5=%quote( a,b,c );
       %let mvar6=%quote(&mvar1);
       %let mvar7=%superq(mvar1);
       %let mvar8=%superq( mvar1 );
       %put _local_;

       %put mvar1: *&mvar1*;
       %put mvar2: *&mvar2*;
       %put mvar3: *&mvar3*;
       %put mvar4: *&mvar4*;
       %put mvar5: *&mvar5*;
       %put mvar6: *&mvar6*;
       %put mvar7: *&mvar7*;
       %put mvar8: *&mvar8*;

       %if &mvar1 eq &mvar2 %then %put A: mvar1 equals to mvar2;
       %if &mvar1 eq &mvar3 %then %put B: mvar1 equals to mvar3;
       %if &mvar1 eq &mvar4 %then %put C: mvar1 equals to mvar4;
       %if &mvar1 eq &mvar5 %then %put D: mvar1 equals to mvar5;
       %if &mvar1 eq &mvar6 %then %put E: mvar1 equals to mvar6;
       %if &mvar1 eq &mvar7 %then %put F: mvar1 equals to mvar7;
       %if &mvar1 eq &mvar8 %then %put G: mvar1 equals to mvar8;
       %if &mvar1 eq %qleft(&mvar5) %then %put H: mvar1 equals to %nrstr(%qleft%()mvar5%str(%));
  %mend;
  %test

  LOG
  TEST MVAR8 _a_b_c_
  TEST MVAR5 _ a_b_c _
  TEST MVAR4 a,b,c
  TEST MVAR7 _a_b_c_
  TEST MVAR6 _a_b_c_
  TEST MVAR1 a,b,c
  TEST MVAR3 _ a,b,c _
  TEST MVAR2 _ a_b_c _
  mvar1: *a,b,c*
  mvar2: * a,b,c *
  mvar3: * a,b,c *
  mvar4: *a,b,c*
  mvar5: * a,b,c *
  mvar6: *a,b,c*
  mvar7: *a,b,c*
  mvar8: *a,b,c*
  A: mvar1 equals to mvar2
  B: mvar1 equals to mvar3
  C: mvar1 equals to mvar4
  E: mvar1 equals to mvar6
  F: mvar1 equals to mvar7
  G: mvar1 equals to mvar8
  H: mvar1 equals to %qleft(mvar5)
  
对于LOG信息的解释:
  • %put _local_;所输出的宏变量的值中,MVAR2和MVAR3是有差别的。其原因在于%STR是在编译阶段起作用。对于MVAR2,%STR作用于字符串” a,b,c ”,在加前置字符和后置字符的同时也会对逗号处理;而对于MVAR3,%STR作用于字符串” &mvar1 ”, 只会加前置和后置的字符,这时不会看到MVAR1的值里的逗号。这里也可以看出,字符串前后的空格都被保留了;
  • %put _local_;所输出的MVAR5和MVAR6也有差别。这个差别在于MVAR5的%LET语句中字符串’ a,b,c ’前后各有一个空格,而MVAR6没有。需要注意的是,这里并不存在像MVAR2和MVAR3之间的差别。这是因为%QUOTE是在宏执行的时候才“工作”,这时会对它参数里宏变量的引用先进行解析,所以%QUOTE看到的都是解析后的值;
  • %put _local_;所输出的MVAR7和MVAR8是没有差别的,这是因为%SUPERQ的参数只是宏变量的名字,所以在宏变量名字前后所加的空格,并不涉及到%SUPERQ的真正作用对象---“宏变量的值”;
  • 通过%put mvar… 语句的输出结果,也可以看出,quote函数会保留字符串前后的空格;
  • 最后对宏变量的比较中,除MVAR5外,其余的7个宏变量比较都是相等的。MVAR5的值和其他的不同,应该是由MVAR5的值中前面的空格引起。如果用%QLEFT把前面空格去掉,就能和MVAR1相等了。

2. 用MLOGIC显示宏参数的值
  options mlogic;
  %macro quotedval(val);
       %put &val;
  %mend;

  %quotedval(a;b;c)
  %quotedval(%str(a;b;c))

LOG
31    %quotedval(a;b;c)
  MLOGIC(QUOTEDVAL):  Beginning  execution.
  MLOGIC(QUOTEDVAL):  Parameter VAL has  value a;b;c
  MLOGIC(QUOTEDVAL):  %PUT &val
  a;b;c
  MLOGIC(QUOTEDVAL):  Ending execution.
  
  32   %quotedval(%str(a;b;c))
  MLOGIC(QUOTEDVAL):  Beginning  execution.
  MLOGIC(QUOTEDVAL):  Parameter VAL has  value _a_b_c_
  MLOGIC(QUOTEDVAL):  %PUT &val
  a;b;c
  MLOGIC(QUOTEDVAL):  Ending execution  

3. 用SYMBOLGEN选项也可以在log中显示某个宏变量的值被quote了
  options symbolgen;
  %let x=%quote(abcde);
  %put &x;

  LOG
  SYMBOLGEN:  Macro  variable X resolves to abcde
  SYMBOLGEN:  Some characters in the above value which were  subject to macro quoting have been unquoted for printing.
  abcde
  



二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

全部回复
2014-7-1 08:21:22
楼 2 of 2

How Long does the Quoting last

以下的情况会解除对特殊字符的quote:
1. 用%UNQUOTE函数;
2. 当使用%SCAN,%SUBSTR,%UPCASE函数来操作quote之后的值时,其返回的值都会失去quote效果;
3. 当quote后的值作为SAS语言代码的一部分时,一般情况下这些代码会自动失去quote效果。只是有个别的情况下,看似没有问题的代码,SAS也不能正常运行,这时就需要用%UNQUOTE函数来恢复特殊字符的功能。(Example 4)

Some Examples

Example 1: %NRSTR and %NRQUOTE
%NRSTR和%NRQUOTE的区别在于:前者在编译阶段起作用,后者在执行阶段起作用。当参数中引用了宏变量时,二者的处理方式不同。%NRSTR不会试图解析宏变量的值,而%NRQUOTE虽然也包含了”NR”,但是宏处理器首先会尽可能地找出宏变量的值,再对结果进行quote。例如下面的例子。
  %let a=b;
  %let b=q;
  %put %nrbquote(&&&a);
  %put %nrstr(&&&a);
  
  %symdel G/nowarn;
  * two  warnings: one from %LET and one from %PUT;
  
%let company1=P&G;
  %put company1: &company1;
  
  * one  warning: from %LET;
  
%let company2=%nrbquote(P&G);
  %put company2: &company2;

  * no warning;
  
%let company3=%nrstr(P&G);
  %put company3: &company3;  

Example 2: %SUPERQ
  %symdel a b c d e/nowarn;
  %let a=&b;
  %let b=&c;
  %let c=d;
  %let d=e;

  %put 1: %str(&a);
  %put 2: %nrstr(&a);
  %put 3: %nrquote(&a);
  %put 4: %superq(a);
  %put 5: %superq(&a);

  %symdel abc/nowarn;
  %superq(abc);  
解释:
  • %put 1:%str不能quote ’&’,因此,运行时宏处理器会一直解析到d;
  • %put 2:%nrstr直接作用在字符串’&a’,并隐藏’&’的功能,因此输出&a;
  • %put 3:首先宏处理器会将’&a’一直解析到d,然后用%nrquote来quote解析的结果d;
  • %put 4:%superq直接quote宏变量a的值,并不做任何解析,因此输出&b;
  • %put 5:首先宏处理器将’&a’一直解析到d,并将d作为%superq的参数,接下来%superq会quote宏变量d的值e。
  • %superq一般情况下不会产生宏变量不能解析的warning,除非作为参数的宏变量本来就不存在。如上面例子中的%superq(abc);

Example 3: %UNQUOTE-1
  %macro mvar;
     a
  %mend;

  %macro putmvar;
      %let %mvar=aaaa;
       %put 1. &a;
       %put 2. &%mvar;
       %put 3. %unquote(&%mvar);
       %put 4. %unquote(%nrstr(&)%mvar);
       %put 5. %unquote(%nrstr(&%mvar));
       %put 6. %unquote(%nrstr(&))%mvar;
       %put 7. %unquote(%nrstr(&))a;
  %mend;
  %putmvar

  lOG
  1. aaaa
  2. &a
  3. &a
  4. aaaa
  5. &a
  6. &a
  7. aaaa
  
这个例子中的宏MVAR会返回a。宏PUTMVAR的%LET语句会将宏变量a赋值为aaaa。
  • %put 1是很简单,直观地将a的值aaaa输出;
  • %put 2输出的是&a,这涉及到宏的编译问题。宏处理器在对宏PUTMVAR进行编译时,遇到&符号后,发现后面跟的并不是作为宏变量需要的字母或下划线。因此,编译后宏处理器不会将这个&符号作为对宏变量的引用,进而在执行阶段也就不会再“等待”后面的宏变量;
  • %put 3的结果和%put 2一样,这里的%UNQUOTE需要在执行时才起作用,而在编译阶段已经认为&符号没有引用宏变量,而且这里并没有quote存在;
  • %put 4中,%NRSTR会在编译阶段隐藏&的功能。到了执行阶段,%UNQUOTE又恢复了&的功能,并和%mvar的结果a连在一起共同作为%UNQUOTE的参数。这样宏处理器就能看到&后面的宏变量名a,进而解析得到aaaa;
  • %put 5没能解析出a的值,因为经过%NRSTR和%UNQUOTE的处理之后,相当于还是把&%mvar交给了%PUT语句,和%put 2一样;
  • %put 6没有解析出a的值,是因为%UNQUOTE恢复&的功能后,后面看到的仍然是%,而不是宏变量的名字;
  • %put 7中%unquote使&符号恢复功能,而且后面连的是a,因此成功输出了a的值aaaa。

Example 4: %UNQUOTE-2
一般来说,当宏处理器产生代码后,回到SAS继续执行时,quote的效果会自动解除。但也有例外的情况,比如:
  data test;
     do num=.,1,2;
        output;
       end;
  run;

  %let mvar=%str(case num
    when . then  "Missing"
    when 1 then "One"
    when 2 then "Two"
    else " "
    end as char);

  proc sql;
     create table test1 as
  
    select distinct num,&mvar
          from test;
  quit;
  
这里提交SQL步后会提示错误信息,但是log中所说的“出错”的代码看上去却没有任何问题。问题可能存在于%str对字符串处理后,影响了SAS对代码的解读。如果改成select distinct num,%unquote(&mvar)就没有问题了。另外,通过测试,将MVAR的赋值改成以下任何一个,SQL步也会正常运行。
  %let mvar=%str(case num
       when %str(.) then   "Missing"
       when 1 then "One"
       when 2 then "Two"
       else " "
       end as char);
  %let mvar=%str(case num
       when 1 then "One"
       when 2 then "Two"
       else "Missing "
       end as char);
  %let mvar=%quote(case num
       when . then  "Missing"
       when 1 then "One"
       when 2 then "Two"
       else " "
       end as char);


二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

2014-7-1 08:49:57
pobel 发表于 2014-7-1 08:21
楼 2 of 2

How Long does the Quoting last
沙发,p大总结的很好

另外我有一个问题,我想把一个字符串宏变量(包括多个单词,用空格分隔)中的每个单词用引号包围,并再用逗号分隔,如下所示:

%let test=Var1 Var2;
%let test=%SYSFUNC(TRANWRD(&test,%STR( ),%STR(',')));
%put &test;

会报错:
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.

即使在单引号前加%也是一样的(按错误提示在两个单引号后各加一个空格是可以的,但那就不是我要的单词了)。所以不知道该怎么办,请p大指教
二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

2014-7-1 11:31:14
playmore 发表于 2014-7-1 08:49
沙发,p大总结的很好

另外我有一个问题,我想把一个字符串宏变量(包括多个单词,用空格分隔)中的每个 ...
试一下用%QSYSFUNC:

%let test=Var1 Var2;
%let test=%QSYSFUNC(TRANWRD(&test,%STR( ),%STR(',')));
%put &test;
二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

2014-7-1 12:58:24
pobel 发表于 2014-7-1 11:31
试一下用%QSYSFUNC:

%let test=Var1 Var2;
多谢,好用

之前我一直用的正则作的替换,正则好就好在用一个\可以转义一切metacharacter了
不像SAS有那么多的宏函数
二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

2014-7-1 15:24:43
个人认为Quoting是SAS Base最难最严密的部分了(相对本人),现在就开始研读下。

另外我有时会遇到一些很恶心的问题,在我有空时,我会模拟出原型,向您提问。

例如,我曾在处理一个连接型宏变量的时候,遇到过类似 %let x = a | b | i' c | half (;
这类问题。

在我用正则取串的时候,例如用%sysfunc(prxchange(&prx.,-1,&x.))的时候,貌似问题直接出在prxchange的中间,
貌似他中间抛出的过程串有时会有干扰。。

我目前用的方法很让步,在prxparse阶段,我被迫使用\'.*?\'?或者\(.*?\)?这种方法来折中,
有时感觉SAS的中间方法不够强,或者最终还是少考虑了哪个小细节,反正源源不断的问题。
二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

点击查看更多内容…
相关推荐
栏目导航
热门文章
推荐文章

说点什么

分享

扫码加好友,拉您进群
各岗位、行业、专业交流群