Why am I getting unbalanced quotes in my macro? - sas

I've got a macro which includes some comments, since I'm good about documenting my code. For some reason, when I run this macro, I get a hanging quote. Why?
Test macro that replicates this:
%macro testme;
* Comment that is in my macro that doesn't work;
proc freq data=sashelp.class;
run;
%mend testme;
%testme;
On the first execution it fails entirely, and on the second it gives me the message ERROR: No matching %MACRO statement for this %MEND statement.

In the SAS Macro language, single line comments aren't treated quite the same as in the base SAS language. Specifically:
*something;
Is not a comment in the SAS macro language! It will be submitted to regular SAS, and will become a comment... but it won't be ignored by the SAS Macro parser, which is where this is a problem. It tokenizes it, which causes it to not ignore the quotation character.
You need to use "PL/1" style comments (ie, block comments) to make this work properly; or just don't use apostrophes (ie, do not instead of don't in comments).
%macro testme;
/* Comment won't break things now!*/
proc freq data=sashelp.class;
run;
%mend testme;
%testme;
See the SAS support article on Using Comments In Macros for more information.

Related

Searching and importing all .csv files in a target directory

I have to scan a target directory, and import every CSV file there into SAS. I am to use separated macros to fix names of those files (they are intentionaly problematic for SAS) and actually import them. I have XCMD disabled, so pipe solution isn't usable for me (the only solution my course provided me, so several exercises are a bit problematic for me). My attempt so far is this:
%let path=my_dir
%macro fixname(badname);
%if %datatyp(%qsubstr(&badname,1,1))=NUMERIC
%then %let badname=_&badname;
%let badname=
%sysfunc(compress(
%sysfunc(translate(&badname,_,%str( ))),,kn));
%substr(&badname,1,%sysfunc(min(%length(&badname),32)))
%mend fixname;
%macro importcsv(file);
options nonotes nosource;
proc import
datafile="%superq(file)"
out=%fixname(%scan(&file,-2,.\)) replace
dbms=csv;
run;
options notes source;
%mend importcsv;
filename folder "&path";
data FilesInFolder;
length Line 8 File $300;
List = dopen('folder');
do Line = 1 to dnum(List);
File = trim(dread(List,Line));
output;
end;
drop list line;
run;
proc sql noprint;
select * into :file1-
from FilesInFolder
where File like '%.csv'
;
quit;
%macro loop;
%do i=1 %to &sqlobs;
call execute(cats('%importcsv('&path.\&&file&i')'));
%end;
%mend loop;
%loop;
Looking at logs there seems to be something wrong with call execute line, but for the life of me I cannot fix it.
As regards the requested call execute line, this will not work.
call execute(cats('%importcsv('&path.\&&file&i')'));
The cats is not properly constructed. It's unclear why you're even using cats, as you aren't really concatenating anything. It's clear why you need to use it, but not why you're currently using it.
First: you have single quotes around the string, which means it won't resolve the macro variables. That is a problem for you. In particular, you want &i to resolve. You also, though, don't want %importcsv itself to resolve.
Second: you have single quotes inside single quotes, or else you have unquoted text.
The right way to do this is:
call execute(cats('%importcsv(',"&path.\&&file&i.",')'));
The goal here is to make sure %importcsv doesn't resolve - so we wrap it in single quotes - but get the macro variables to resolve - wrapped in double quotes. It would be okay for &path not to resolve (as it's a global variable), but &&file&i absolutely needs to, since it's what you are looping over.
The final ')' is unnecessary (it could be included inside the double quotes), but I like it for readability (it makes it obvious that it's the end of the macro invocation). In this case it's unimportant, but sometimes the middle bits have more parentheses in them, and it becomes unclear when you have the right number.
All this aside, you're making this much too complicated. Here:
proc sql noprint;
select * into :file1-
from FilesInFolder
where File like '%.csv'
;
quit;
Why not simply put the macro invocation itself?
proc sql noprint;
select cats('%importcsv(',"&path.\",file,')')
into :filelist separated by ' '
from FilesInFolder
where File like '%.csv'
;
quit;
&filelist
Now you have &filelist which contains all of the macro invocations, and you can just invoke them, no macro loop needed.
You can check out my presentation on Writing Code With Your Data if you want to know more about how that works.
Of course, if this is homework and you have to do the first way, it's fine, but in the real world the second way is superior unless you have > 60k characters worth of macro calls.

SAS adding unwanted spaces to file names when macro variables are included

I'm creating a text file with SAS and I'm using a macro variable with a date in my text file's name to make it distinct from other similar files.
The problem I'm experiencing:
SAS is adding two unwanted spaces in the middle of the file name. The unwanted spaces are placed directly before the text generated by my macro variable
I'm certain this has everything to do with my macro variable being used, but on its own, the variable doesn't contain any spaces. Below is my code:
proc format;
picture dateFormat
other = '%Y%0m%0d%0H%0M' (datatype=datetime);
run;
data _null_;
dateTime=datetime();
call symput('dateTime', put(dateTime,dateFormat.));
run;
%LET FILE = text_text_abc_&dateTime..txt;
filename out "/location/here/&FILE" termstr=crlf;
data _null_; set flatfile;
/*file content is created in here*/
run;
The exported file name will look like this:
NOTE: The file OUT is:
Filename=/location/here/text_text_abc_ 201702010855.txt
If it helps, I'm using SAS E-Guide 7.1.
Any help is appreciated! Thanks, all!
You need to assign an appropriate default length to your picture format. SAS is applying a default default length of 14 but you need 12, e.g.
proc format;
picture dateFormat (default=12)
other = '%Y%0m%0d%0H%0M' (datatype=datetime);
run;
Use call symputx() instead of call symput(), then SAS will automatically strip the leading and trailing blanks from the value written to the macro variable. You should really only use call symput() in the rare cases where you want the macro variable value to have leading or trailing blanks.
Run this little program to see the difference.
data _null_;
str=' XX ';
call symput('var1',str);
call symputX('var2',str);
run;
%put |&var1|;
%put |&var2|;

Why proc does not need a % symbol when written inside a SAS macro

I have a basic question about SAS macro. Inside sas macro, when you write a let statement or a put statement or an if statement, you always prefix it with %.
But when you write a 'proc' inside a macro, why don't we need to write %proc?
Or for example %data?
Because the data step language and macro language are two different programming environments. When SAS tokenizes your statements, it looks for specific keywords. One of those keywords is the % trigger. Before anything is run, the word scanner separates out macro statements from SAS statements and passes them to the appropriate processor. Macro statements are always compiled and resolved before SAS statements.
When you are working with macros, you're storing text strings in either some macro variable or macro program. Anything inside of the macro variable or program is raw text as far as SAS is concerned.
Consider the following two macros:
Macro 1:
%macro foo1;
data bar1;
var1 = 'a';
var2 = 'b';
var3 = 'c';
var4 = 'd';
keep var1-var3;
run;
%mend;
Macro 2:
%macro foo2;
keep
%do i = 1 %to 3;
var&i
%end;
%mend;
data bar2;
var1 = 'a';
var2 = 'b';
var3 = 'c';
var4 = 'd';
%foo2;
run;
When you compile Macro 1, nothing happens until you call it. This is because you have stored all of that text inside of a macro program. When you call the macro statement:
%foo1;
SAS passes the phrase foo1 to the macro processor, the program is run, and the resolved text is spat back into the word scanner, which then processes the individual tokens one by one. As far as SAS is concerned, it sees exactly the datastep contained inside:
data bar1;
var1 = 'a';
var2 = 'b';
var3 = 'c';
var4 = 'd';
keep var1-var3;
run;
When we run Macro 2, we'll get the exact same output, but it's being performed differently.
When we compile the macro foo2, we also store some information about an internal macro loop. This particular loop simply creates the text "var1 var2 var3" sequentially. Notice that there's a piece of text right before the loop: "keep." This is completely valid since it's just a piece of text.
We execute the macro inside of the data step. When we start the compilation process of the data step, the word scanner finds the macro trigger % and passes that information along to the macro processor. When it finds that foo2 is a valid compiled macro, the macro processor runs the macro program, and the resulting text is sent over to the word scanner:
keep
var1
var2
var3
We end invocation of the macro with a semicolon on purpose. This tells the word scanner that we are at the end of a statement, which eventually gets sent over to the compiler.
Once the macro is complete, the word scanner keeps chugging along, passing statements to the input stack until it hits the run boundary, or another proc step.
We could invoke foo2 outside of a data step, but SAS will error out saying that they're not valid statements. It's equivalent to typing keep var1 var2 var3; on a line and trying to run it. That specific piece of text is only useful inside of a data step, though SAS will happily attempt to run it wherever you want.
SAS doesn't see macros. It only understands data step and proc language. Only the macro processor can see and make use of macro triggers. The word scanner prevents the compiler from seeing any of the macro triggers. Think of the word scanner like a special filter: it hands out text only to the places that can read it.
There are a few exceptions where some functions in the data step can bridge the gap between SAS and macros, but it doesn't really have anything to do with this.
% denotes macro syntax - macro functions, macro statements, or macro commands. Basically, things that would be covered by the SAS Macro Language Reference.
When you have a proc in a macro, what you're asking the macro to do is to type that proc onto the stack as if you'd typed it. You don't need a % because the proc is the text you're asking be typed, not a command to the macro language interpreter itself.
SAS Macro language and SAS Base are two essentially separate languages - the latter is the core of SAS, the former is a helper that can make it easier to do certain repetitive things. They're only loosely integrated.
The %let or %put are macro statements: they are not the same thing as the put you can use in the data step. They share the name and basic idea of functionality, but nothing else any more than printf in c and printf in r share in common.
For the same reason that I don't have to type <> around the words in my webpage when I am editing the HTML. The words in my webpage are not HTML commands the same way that SAS code statements are not commands to the SAS macro processor.

SAS: create list of strings in quotation marks as macro variable for input filtration

I'd like to use the following syntax
data new;
set old (where=(mystring in ('string1','string2',...,'string500')));
run;
in order to filter a very large input data set. The 500 strings at first are contained as numeric values in the variable "bbb" in the dataset "aux". So far I have created a macro variable which contains the required list of the 500 strings the following way:
proc sql noprint;
select bbb into :StringList1 separated by "',' "
from work.aux;
quit;
data _null_; call symputx('StringList2',compress("'&StringList1'")); run;
data new;
set old (where=(mystring in (&StringList2)));
run;
... which seems to work. But there is a warning telling me that
The quoted string currently being processed has become more than 262
characters long. You might have unbalanced quotation marks.
Results still seem to be plausible. Should I be worried that one day results might become wrong?
More importantly: I try to find a way to avoid using the compress function by setting up the
separated by "',' "
option in a way that does not contain blanks in the first place. Unfortunately the following seems not to work:
separated by "','"
It doesn't give me a eror message but when looking at the macro variable there is a multipage-mess of red line numbers (the color which usually denotes error messages), empty rows, minus signs, ... . The following screenshot shows part of the log after running this code:
proc sql noprint;
select vnr into :StringVar1 separated by "','"
from work.var_nr_import;
quit;
%put &StringVar1.;
Have already tried to make use of the STR()-function but no success so far.
I cannot replicate your error messages in SAS 9.3
If your variable is numeric you don't need quotes in the macro variable.
If it is character try using the QUOTE() function.
proc sql noprint;
select quote(bbb) into :StringList1 separated by " "
from work.aux;
quit;
A macro variable can only contain 65,534 characters. So if there are too many values of BBB then your macro variable value will be truncated. This could lead to unbalanced quotes. That is most likely the source of your errors.
Note that you can turn off the warning about the length of the quoted strings by using the NOQUOTELENMAX system option, but in this application you wouldn't want to because the individual quoted strings are not that long.
You will be better served to use another method to subset your data if lists this long are required.
This will work,
for double quotations
proc sql noprint;
select quote(bbb) into :StringList1 separated by ","
from work.aux;
quit;
for single quotations
proc sql noprint;
select "'"||bb||"'" into :StringList1 separated by ","
from work.aux;
quit;

SAS ODS escape character macro variable error

The SAS v9.4 documentation lists an automatic macro variable &sysodsescapechar which contains the current ODS escape character, assigned using ods escapechar=.
Whenever I try to view the macro variable using a %put statement, I get the following error:
ERROR: Open code statement recursion detected.
This happens when open code erroneously causes a macro statement to call another macro statement.
I've tried all of the following:
%put &=sysodsescapechar.;
%put %nrbquote(&sysodsescapechar.);
%put %superq(sysodsescapechar);
They all result in the same error.
When I try to view the macro variable using a data step, it appears to be empty.
data test;
esc = "&sysodsescapechar.";
put esc=;
run;
If the macro variable actually is empty, why do I get open code statement recursion errors? The %put statement on its own is valid, so putting an empty variable shouldn't be an issue.
Any guidance here would be much appreciated.
What's happening is the escape char seems to need a close parentheses. For example:
%put %superq(SYSODSESCAPECHAR););
;
It escapes the ) , which means now you have
%put superq(;);
In your first example, it's a little trickier because a semicolon by itself doesn't seem to be escaped so you have to provide a close parentheses:
%put &SYSODSESCAPECHAR.)x;
x
That works, for example. I'm not sure if it's only close paren or other things that would also allow it to stop trying to escape, but that's the only thing I can tell works.
You can look at the actual value of the macro variable in SASHELP.VMACRO; it's not ' ' (which is indeed what gets passed to the data step even with SYMGET, but it's clearly parsed). In that table it is '03'x, which looks like a uppercase L in the upper half of the character. That is the "End of Text" control character. I suspect the behavior in the editor when using this in text (in a macro variable) is simply a behavior of the editor - '03'x is not representable on many editors (if I try to paste it here, for example, it isn't displayed, but does exist as something I can backspace over with zero width). SAS is clearly capable of dealing with a 'normal' ods escapechar but isn't capable of dealing with '03'x in the same fashion.