SAS Macro: Symbols of Frustration? %Let us help! A Guide toDebugging Macros SAS Macro DEBUG:SAS宏测试手册 原文地址: http://www2./proceedings/sugi29/128-29.pdf转载请注明出处: http://blog.sina.cn/dpool/blog/s/blog_5d3b177c0100c4hd.html?vt=4这篇文章主要讲了如何测试宏,以及介绍了一点编写宏代码时常犯的错误。 1如何测试宏(HOUSTON, WE HAVE A PROBLEM?!) 测试宏第一个重大的困难是不容易发现宏的错误在哪里,宏代码在运行时,在日志里可能会出现错误或警告等提示,但是也可能什么也不提醒,只是得到了非预期的结果。例如下面几个例子: %macro wrttxt(text=Something we really want to write to thelog!); %put text; %mend wrttxt; %wrttxt ; 在日志中的输出结果是text,而不是我们想得到的Something we really want to write to thelog!原因是代码中没有在text前加’&’ 再看下面这个例子: %macro wrttxt(text=Something we really want to write to thelog!); %put &txt; %mend wrttxt; %wrttxt ; 在日志的输出结果为: WARNING: 没有解析符号引用 TXT。 &txt 亦不是我们想要得到的结果,原因是将text错误地拼写成了txt 下面这个例子才是得到我们想要的结果: %macro wrttxt(text=Something we really want to write to thelog!); %put &text; %mend wrttxt; %wrttxt 2 系统选项: 最常用的四个系统选项包括:Options symbolgen mlogic mprint mfile 关掉这些系统选项的方法:Options NoSymbolgen nomlogic nomprint nomfile; 后面三个选项mlogic mprint mfile在上一篇文章已经介绍,这里只介绍symbolgen选项。 symbolgen选项 该选项将在日志中告诉你每个宏变量在每一步中的值是多少,这样我们可以通过追踪宏变量值的变化情况来判断我们程序设计的逻辑、程序的运行等是否正确。例如下面的代码: options symbolgen; %macro bighouse(Pet=Cat Dog, Type=Fat Fuzzy, Npets=2, Ntypes=2); %do i=1 %to &npets; %do j=1 %to &ntypes; %let allpets=%scan(&type,&i) %scan(&pet,&j); %put &allpets; %end; %end; %mend bighouse; %bighouse ; 运行后日志的结果为: SYMBOLGEN: 宏变量 NPETS 解析为 2 SYMBOLGEN: 宏变量 NTYPES 解析为 2 SYMBOLGEN: 宏变量 TYPE 解析为 Fat Fuzzy SYMBOLGEN: 宏变量 I 解析为 1 SYMBOLGEN: 宏变量 PET 解析为 Cat Dog SYMBOLGEN: 宏变量 J 解析为 1 SYMBOLGEN: 宏变量 ALLPETS 解析为 Fat Cat Fat Cat SYMBOLGEN: 宏变量 TYPE 解析为 Fat Fuzzy SYMBOLGEN: 宏变量 I 解析为 1 SYMBOLGEN: 宏变量 PET 解析为 Cat Dog SYMBOLGEN: 宏变量 J 解析为 2 SYMBOLGEN: 宏变量 ALLPETS 解析为 Fat Dog Fat Dog SYMBOLGEN: 宏变量 NTYPES 解析为 2 SYMBOLGEN: 宏变量 TYPE 解析为 Fat Fuzzy SYMBOLGEN: 宏变量 I 解析为 2 SYMBOLGEN: 宏变量 PET 解析为 Cat Dog SYMBOLGEN: 宏变量 J 解析为 1 SYMBOLGEN: 宏变量 ALLPETS 解析为 Fuzzy Cat Fuzzy Cat SYMBOLGEN: 宏变量 TYPE 解析为 Fat Fuzzy SYMBOLGEN: 宏变量 I 解析为 2 SYMBOLGEN: 宏变量 PET 解析为 Cat Dog SYMBOLGEN: 宏变量 J 解析为 2 SYMBOLGEN: 宏变量 ALLPETS 解析为 Fuzzy Dog Fuzzy Dog 3 介绍一些宏编程时经常遇到的问题 前面两个在以前的文章里已有介绍,因此在本文中就不再讲了。 3.1 缺失值与NULL值的比较 在数据步时,并没有NULL值,字符的缺失值用””表示,而数字的缺失值用.来表示。但是在宏语言中,没有表示缺失值的方法。如果在宏语言中,一个宏变量的值为NULL,其值就是NULL。 例如在数据步中,if gender = “ “ then do;,这一句就表示gender如果为空时的情况。而在宏语言中,%if&age = “ “ %then%do;这一句就表示如果&age的值如果为双引号时,而不是空值时的情况。如果要表示空值,则可以用下面语句:%if&age = %then %do;。但是由于这一句的=后面为空,不是很好的编程习惯,因此,一般的解决方法如下: %macro sillytest(age=); %if '&age' = '' %then %do; %put This comparison worked; %end; %else %do ; %put I guess it did not work; %end; %mend; %sillytest; 更好的解决方法是用宏函数%str: %macro sillytest(age=); %if &age = %str() %then %do; %put This comparison worked; %end; %else %do ; %put I guess it did not work; %end; %mend; %sillytest; 3.2 SCAN与%SCAN 和AND %QSCAN的比较 %SCAN和SCAN函数是非常有用的,特别是处理用多个同一符号分隔的长字符串时。例如下面的程序产生的字符串: proc sql; select name into: names separated by ' ' from SASHelp.class;quit; %put &Sqlobs Names added to Macro variable (array)%nrstr(&NAMES) ; 这里第二行的names宏变量就是包含了SASHelp.class表中的全部name的值,并且以' '分隔开。 为了演示SCAN与%SCAN的区别,我们看一看下面两个例子: data _null_; test='AA B'C D'C'; i=1; do until(scan(test,i,' ')=' '); word=scan(test,i,' '); put word; i=i+1; end; run; SAS日志的输出为: AA B'C D'C %macro breakstrg(string=); %let i=1; %do %until (%qscan(&string,&i,' ')= %str()); %let word=%qscan(&string,&i,' '); %put &word; %let i=%eval_r(&i+1); %end; %mend; %breakstrg(string=AA B'C D'C); 这时SAS日志的输出为: AA B C D C 为什么会出现这样的结果。其实原因以前也讲过,就是SAS宏将所有的字符都当做文本来处理,而没有特殊的含义。因此在%letword=%qscan(&string,&i,' '); 这一句中,最后面的''被当作了’’和空格两个分隔符,这样,无论是’或是空格都可以分隔原来的字符串,因此产生了如上的输出结果。解决方案跟IF一样,如下: %macro breakstrg(string=); %let i=1; %do %until (%qscan(&string,&i,%str( ))= %str()); %let word=%qscan(&string,&i,%str( )); %put &word; %let i=%eval_r(&i+1); %end; %mend; %breakstrg(string=AA B'C D'C) ; 其结果与数据步的例子一致。 3.3 宏数学计算问题 在SAS宏里面数学计算主要用到%SYSEVALF和%EVAL两种运算,%EVAL用于整数计算,而%SYSEVALF用于浮点数计算。如果我们用%EVAL计算浮点数,则会出现下面错误: ERROR: A character operand was found in the %EVAL function or %IFcondition where a numeric operand is required. The condition was:__________ 在宏里进行数学计算时一定要非常小心,因为宏里的数学计算与数据步的数学计算有一些差异的,例如: %Macro checkit(val=); %if -5 le &val le 0 %then %put &val is in neg range (-5 to0); %else %if 1 le &val le 5 %then %put &val is in pos range (1 to5); %else %put &Val is WAY out of Range; %mend checkit; %checkit(val=-10); %checkit(val=-2); %checkit(val=2); %checkit(val=10); 结果如下: 106 %checkit(val=-10); -10 is in neg range (-5 to 0) 107 %checkit(val=-2); -2 is in pos range (1 to 5) 108 %checkit(val=2); 2 is in pos range (1 to 5) 109 %checkit(val=10); 10 is in pos range (1 to 5) 结果并未如预期出现。我们再看看数据步里的计算结果: data _null_; do val=-10,-2,2,10; if -5 le val le 0 then do; put Val ' is in the negative range'; end; else if 1 le val le 5 then do; put Val ' is in the positive range'; end; else do; put Val ' is WAY out of range'; end; end; run; 结果如下: -10 is WAY out of range -2 is in the negative range 2 is in the positive range 10 is WAY out of range 这里的结果是我们要想得到的,为什么在宏语言里出现了非预期的结果呢。原因是在数据步里的语句if -5 le val and val le0 then do;.在宏里成为了%if (-5 le &val) le 0 %then %put &val isin neg range (-5 to 0);,先判断(-5 le&val)是否为真,真则为1,假则为0,然后再判断刚才的结果与le 0进行判断是否为真,因些当我们输入-10时,就成了%if(-5 le -10) le 0 %then %put &val is in neg range (-5 to0);,显然-5>-10,因此(-5 le -10)为假,其结果为0,然后再判断0是否le 0,这个结果为真,也就出现了-10is in neg range (-5 to 0)这个结果的输出。 解决的方法: %Macro checkit(val=); %if -5 le &val and &val le 0 %then %put &val is in negrange (-5 to 0); %else %if 1 le &val and &val le 5 %then %put &val is in posrange (1 to 5); %else %put &Val is WAY out of Range; %mend checkit; 也即是最后用完全输入的形式输入,而不要用简化的形式,要养成良好的编程习惯。 3.4 SAS宏注释问题 我们知道,SAS的注释一般有两种形式,一种是*;,另一种是,在数据步时,这两种注释功能基本一致,但是在SAS宏编程中,*;这种注释可能出现问题,例如下面的例子: %macro tryit; *%let dattype=ae; %let dattype=demog; *%let dattype=meds; ***********************; %put data type is &dattype; %mend tryit; %tryit ; 在日志中的输出结果为: data type is meds 为什么结果不是data type isdemog。原因是在数据步时,当出现*时,SAS编译器就得到一个KEYWORD,告诉编译器这个星号直到下一个分号;之间的内容都是注释。但是在SAS宏代码时,由于SAS宏编译器并不将星号*当作是一个KEYWORD,因此,*只是一个token(详见SAS宏编译原理),这时,其后面的代码将不会作为注释,因此dattype被赋值了三次,其最后值为最后的赋值语句赋予,也即是后日志的输出结果。 修改方法是在注释的星号前加一百分号%,这样SAS宏会将%*当作一个KEYWORD进行编译。另一个方法就是用来进行注释: %macro tryit; %*let dattype=ae; %let dattype=demog; %*let dattype=meds; ***********************; %put data type is &dattype; %mend tryit; %tryit ; 这里日志输出的结果为:data type is demog,即我们预期的结果 4 其它宏问题 4.1 全局宏变量与局部宏变量 这个已有文章专门讨论,就不再讲了。 4.2 宏变量撞车 就是宏变量取了相同的名字,这时候很容易出现无意间修改了宏变量的值,从而产生一些我们非预期的结果。这个上一篇宏测试文章里已有介绍,主要避免的方法是尽量少用global宏变量,以及对局部宏变量都用local进行定义,然后就是尽量分析一下宏变量,不要取重名,特别是循环变量的命名。 4.3 其它宏问题 例如可能没有与%macro相对应的%mend语句。例如下面的例子: %macro quoted(Text=A Problem with Kevin's Macro); %put &text; %mend; %QUOTED; 这里之后以不会执行,是因为第一行的单引号,使得后面所有的代码都成了该单引号未能匹配的另一个单引号之间的内容。这种问题一般经常遇到,但是没有经验的话可能想半天也找不出解决思路。 我们再看一个例子: %Macro wrdcnt(string); %let cnt=0; %do %while(%scan(&String,&cnt+1,%str( ) ne %str()); %let cnt=%eval_r(&cnt+1); %end; &cnt; %MEND WRDCNT; 这时日志报的错是:WARNING: 当前正处理的引用字符串比 262 个字符长。您可能有不平衡的引号。 其实这里的错误的原因仅仅是在第三行的第一个%str( )的后面少了一个)而已。
|