漫談SAS Macro (3) - Macro Quoting, Part I
所謂的Macro Quoting,就是%STR, %NRSTR, %NRBQUOTE, %BQUOTE一類的Macro Function。介紹這類函數一個非常常見的例子是,假定想用%LET語句為宏變數a賦值1;2:
%let a = 1;2;n%put &a;nn============================================================================n%let a = 1; 2;n -n 180nERROR 180-322: Statement is not valid or it is used out of proper order.nn%put &a;n1n
ERROR產生的原因其實顯而易見: 如果不顯式的指出1與2之間的分號是值的一部分的話,它就會被SAS認為是%LET語句的結束標誌,於是&a的值是1。而分號之後的2;則被認為是非法語句。 這裡面分號是一個特殊字元,當它作為宏變數的值的一部分存在時,可能會影響SAS編譯一段代碼的方式。
%let a = %str(1;2);n%put &a;nn================n1;2n
加上%STR之後就能順利運行。類似的特殊字元有很多:
+-*/<>=^|~,;()&%"n
除了這些特殊字元以外,還有一類被稱為mnemonics的字元串,包括
blank AND OR NOT EQ NE LE LT GE GT INn
這些mnemonics有時候也會造成意想不到的麻煩,舉例如下:
%let a = a and b;n%put &a;n%let b = %eval(&a ne);nn================================================================================na and bnERROR: A character operand was found in the %EVAL function or %IF condition where a numierc operand is required. The condition was: a and b nen
這裡面的錯誤源於SAS在evaluate括弧裡面的值時,遇到and會將之認為是「邏輯與 」,從而a邏輯與b的值,進而引發錯誤。需要指出的是,將&a ne 寫到%IF語句中會引發一樣的錯誤。
1.Macro Quoting的原理
Quoting function在作用時,先把值的兩端各加上一個特殊的位元組,叫做delta character,用來標識mask的開始和結束。不同的quoting function使用不同的開始和結束標識。
%STR: x01 x02n%NRSTR: x01 x02n%BQUOTE: x04 x08n%NRBQUTOE: x06 x08n
在值的內部,特殊字元則被替換成其他他字元,這裡只看一個例子:
%let a = %str(a+b);nndata _null_;n set sashelp.vmacro;n if name = A then put value $hex10.;nrun;nn================================================================================n0161156202n
可以看到+(x2B)被替換成了x15。如果感興趣可以是使用類似的program將所有的特殊字元的替代字元列印出來。
2. Quoting function的類型
可以依照作用時間將上述quoting function分成兩類:compliation function和execution function。前者的mask作用發生在編譯期間,後者發生在運行期間。關於編譯和運行的概念,這裡不深入討論了,僅提供一個例子:
%macro getMaxVal(indata, invar); n /* returns maxium value of a variable from a dataset*/n %if %symexist(_max_) %then %let _max_ =;n %global _max_;n proc sql noprint;n select max(&invar) into: _max_n from &indata;n quit;n%mend getMaxval;nndata class;n length sexl $10 sexn 8;n set sashelp.class;n call missing(sexl, sexn);nrun;nndata notnull;n length max $200;n set sashelp.vcolumn;n where libname = WORK and memname=CLASS;n call execute(%getMaxVal(||strip(memname)||, ||strip(name)||));n max = strip("&_max_");n if type =char and max = " " then delete;n else if type = num and max = "." then delete;n keep memname name type max;nrun;n
在上面的例子中首先定義了一個宏%getMaxVal,從給定的data中返回某變數的最大值。然後基於SASHELP.CLASS構造了一個測試數據集,包含一個數值型空變數和字元型空變數。結尾的data步的思路是循環檢查測試數據集中各變數的最大值,如果最大值是空值則刪掉該變數所代表的觀測。然而,當你首次運行這個macro的時候,首先會發現log中的warning,注意這個warning的位置:
18 data notnull;n19 length max $200;n20 set sashelp.vcolumn;n21 where libname = WORK and memname=CLASS;n22 call execute(%getMaxVal(||strip(memname)||, ||strip(name)||));n23 max = strip("&_max_");nWARNING: Apparent symbolic reference _MAX_ not resolved.n24 if type =char and max = " " then delete;n25 else if type = num and max = "." then delete;n26 keep memname name type max;n27 run;n
如果你打開運行後的數據集notnull會發現,變數max在所有的觀測上的值都是&_max_,也就是說這個宏變數沒有解析。為什麼呢?因為當你submit這段程序時,程序首先要被編譯。在編譯期間,SAS遇到
max = strip("&_max_");n
時會從全局宏變數中查找宏變數_max_。這時這個變數還不存在,因為
call execute(%getMaxVal(||strip(memname)||, ||strip(name)||));n
要等到程序運行期間才能真正執行,宏變數_max_才能由宏 %getMaxVal創建。如果你嘗試再次運行該段程序,會發現這個warning不見了,而打開數據集notnull,則發現變數max在每個觀測上面的值均是150。多次運行的結果均是如此,至於原因就留給大家自己思考了。
%STR和%NRSTR的作用發生在編譯期間,%BQUOTE和%NRBQUOTE的作用發生在運行期間。這裡總結幾點這幾個函數在用法上面的異同:
1) %NRSTR相比%STR,會額外屏蔽字元&和字元%。%NRBQUOTE和%BQUOTE類似。函數名開頭的NR表示Not Resolved。
2) %STR和%BQUOTE除了作用時間的差異以外,%BQUOTE函數會忽略不成對的括弧,單引號和雙引號。而%STR函數不具有這個功能,如果其作用的字元串中有不成對的括弧,單引號或者雙引號,則需要在該字元前加上%號。比如:
%let a = %str(a%b); ** a % must be added to unmatched single quotation mark;n%let b = %bquote(ab);n%put %NRSTR(&a): &a;n%put %NRSTR(&b): &b;n===============================================================================na: abnb: abn
3) 因為%STR的作用發生在編譯期間,所以它往往用來屏蔽一個常字元串(constant string)。類似於%str(&a)的寫法其實沒有太大的意義:
%macro test(val);n %let a= %str(&val);n data _null_;n set sashelp.vmacro;n if name = A then put value $hex10.;n run;n%mend test;nn%test(a+b);n================================================================================n01412B4202n
和上面的例子比較,可以看到,+並沒有被替換成字元x15。這是因為%STR的作用發生在編譯期間,macro test裡面的%let語句在編譯時相當於%let a = x01&valx02。 此時&val的值還未被解析(因為編譯的時候宏參數val的值還未定)。等到macro運行期間,參數的值確定,於是%let a = x01a+bx02。這樣就造成了上面的結果。實際上這個例子的結果非常重要,考慮下面一個更加實際的macro:
%macro isNull(val); %* check if input value is null; n %if %nrbquote(&val) ne %then FALSE;n %else TRUE;n%mend isNull;nn%put %isNull(a+b); ** correct;n================================================================================nFALSEnnn%macro isNull2(val); %* check if input value is null; n %if %str(&val) ne %then FALSE;n %else TRUE;n%mend isNull2;nn%put %isNull2(a+b); ** incorrect. syntax error;n================================================================================nERROR: A character operand was found in the %EVAL function or %IF condition where a numeric operand is required. Then condition was: &val nenERROR: The macro ISNULL2 will stop executing.n
因為val可能會解析成任何值,所以%STR不是一個robust的選擇。
4) 因為%NRSTR是發生在編譯期間,而且會屏蔽%和&,所以適合用來屏蔽一串可能含有%或&的長字元串,並且字元串中&和%並非宏變數或者%的trigger。
data report(label="%nrstr(%change from baseline)");n /* more steps */nrun;n
5)由於 %BQUOTE和%NRBQUOTE的作用發生在運行期間,所以括弧內的宏變數或者宏會儘可能的解析。
data _null_;n call symput(client, Johnson&Johnson);nrun;nn%let fullstr = %bquote(Client: &client);nn%let fullstr2 = %nrbquote(Client: &client);nn================================================================================n6 data _null_;n7 call symput(client, Johnson&Johnson);n8 run;nnNOTE: DATA statement used (Total process time):n real time 0.00 secondsn cpu time 0.01 secondsnn11 %let fullstr = %bquote(Client: &client);nWARNING: Apparent symbolic reference JOHNSON not resolved.nWARNING: Apparent symbolic reference JOHNSON not resolved.n13 %let fullstr2 = %nrbquote(Client: &client);nWARNING: Apparent symbolic reference JOHNSON not resolved.n
先講講為什麼fullstr2下面會有一個warning。當
%let fullstr2 = %nrbquote(Client: &client);n
被編譯時,&client被解析成Johnson&Johnson.&Johnson觸發了編譯器尋找宏變數johnson,而這個宏變數實際上不存在,於是編譯器發出一個warning。在執行階段,由於%NRBQUOTE屏蔽字元&,不再有新的warning產生。這也順便解釋了為什麼第一個%let語句下面有兩個warning。我們用sashelp.vmacro來檢驗我們的理論:
data _null_;n set sashelp.vmacro;n if name=FULLSTR then put FULLSTR: value $hex50.;n if name=FULLSTR2 then put FULLSTR2: value $hex50.;nrun;n================================================================================n FULLSTR: 04436C69656E743A204A6F686E736F6E 26 4A6F686E736F6E08nFULLSTR2: 06436C69656E743A204A6F686E736F6E 0F 4A6F686E736F6E08n
注意輸出結果中用空格分開的部分,FULLSTR中&沒有被mask,而FULLSTR2中&則被替換成了x0F.
==================================分割線=============================
最近一段時間稍微有點忙,而且又有點犯懶,而實際寫本節時這些例子也想了很久,故而好久未更新。限於篇幅,本節還有一半的內容下次再更。到時再見。
推薦閱讀:
※ORDER=DATA in PROC REPORT
※SAS 是一個什麼樣的公司,為什麼常年位於最佳僱主前幾名?
※SAS入門指南
※sas界面為什麼那麼丑?
※SAS入門書籍有哪些值得推薦?