SAS ODS escape character macro variable error - sas

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.

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 does not evaluate the content of %PUT

I use this bit outside of any DATA step.
%let sth = 20191111;
%let sthelse=SUBSTR(INPUT(&sth.,12.),1,4);
%put &sthelse.;
It does not yield '2019', which I would expect but rather
SUBSTR(INPUT(20191111,12.),1,4)
What goes wrong here?
Use the macro function %SUBSTR to extract a portion of its argument. Remember, macro values are only character values (not to be confused with data step character variables and values) and have no explicit numeric value, even when the macro value is comprised of all digits.
%let sth = 20191111;
%let first4 = %substr(&sth,1,4);
You are not understanding how the macro pre-processor works. It is just a text replacement tool. It looks for two trigger character, % and & to see where it needs to do work. You can see both at work in your statement:
%let sthelse=SUBSTR(INPUT(&sth.,12.),1,4);
So the % will trigger the macro processor and it will recognize %let as a macro statement. Then the & trigger will cause it to treat &sth. as a macro variable reference to be replaced. So it replaces that and you end up with this statement.
%let sthelse=SUBSTR(INPUT(20191111,12.),1,4);
Since there are no more macro triggers SAS happily stores that text into the macro variable sthelse.
You also seem confused about how numbers and character strings work. If you write this code:
data x;
x=INPUT("20191111",12.);
run;
You would be asking SAS to convert the string 20191111 into the number 20,191,111. So X would be a numeric variable. So it would have the same effect as if you ran this statement instead.
x=20191111;
Now if you then ask SAS to try to use the SUBSTR() function on that NUMERIC value by doing this:
y=substr(x,1,4);
SAS will need to first convert X into a character string. It will happily do that for you, but it will use the BEST12. format to make the conversion. So your number 20,191,111 will become the string
20191111
So it will have 4 leading spaces. Then if you take the first four characters you end up with four spaces.
Did you intend to run this code instead?
%let sth = 20191111;
%let sthelse=%substr(&sth,1,4);
That will use the MACRO function %SUBSTR() to take the first four characters of the string you have given it. Since you gave it the string 20191111 (to the macro processor everything is a string, no quotes needed) the result will be the string 2019.
SUBSTR is datastep function.
Try to use it into macro function:
%let sth = 20191111;
%let sthelse=%sysfunc(SUBSTR(%sysfunc(INPUTn(&sth.,12.)),1,4));
%put &sthelse.;
There isn't macro function named substr and input, this function is "data step functions". You should use %sysfunc statement. And macro variables stores as text, so you shouldn't use input function on it.
%let sth = 20191111;
%let sthelse=%sysfunc(SUBSTR(&sth.,1,4));
%put &=sthelse.;
STHELSE=2019
UPDATE(thanks #Tom):
There is macro function %substr, so you can use %SUBSTR(&sth.,1,4) instead of %sysfunc(SUBSTR(&sth.,1,4)).

Find current value of ODS ESCAPECHAR

I am writing a macro to help create documentation which must use ods escapechar. I need it to preserve whatever options/setting the original program used. However, setting a new escape character in the macro overwrites the original program's escape character. How can I change the escape character only for the duration of my macro?
Is there a table somewhere in SAS which stores the current ods escapechar? My thought is to assign the current value to a macro variable and use that to reassign it once my process is complete.
Use the escape sequence instead
(*ESC*)
Then you don't have to know or reset anything.
Somewhere around 9.3 (?) they added &SYSODSescapeChar:
%put &=SYSODSescapeChar ;
SYSODSESCAPECHAR=^
But since the generic (*ESC*) mentioned by data_null_ came first, I'm in the habit of using that, rather than do the save option / reset option / restore saved option dance.

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.

Unmatched quotation mark issue in SAS

As is known to all, SAS needs special care to quotation marks inside a sentence.
E.g.
%let quoted="I'd like to";
data temp;
set temp;
quoted="&quoted";
run;
error is encounterred when submitting.
In fact I need to copy data to one dataset from another one, in which there are a lot of records containing quotation marks. When assigning, error occurrs and data step stop executing, causing rest of the code to be invalid. So in this case, it's impossible to modify original data set by adding duplicated quotation marks, which doesn't make sense.
So instead of having to add a duplicated one, like, "I''d like to", is there any other way of avoiding the error, or making data step keeping executing?
Thanks,
When using the macro language (including the %let command) you do not want to use quotes to identify text strings. To place a single quote in a string you must use one of the macro utility masking functions such as %str(). The correct syntax to place a single unmatched quote in a macro variable using %let is shown below. The % symbol before the single quote is an escape character to tell SAS that the following character (a single quote) should be used as a literal. Also note that I've removed the double quotes from the %let as they are not required.
%let quoted=%str(I%'d like to);
data temp;
quoted="&quoted";
run;
Cheers
Rob
I'm not sure what you're trying to achieve in the actual situation, but in the above situation it can be solved removing the double quotation marks in the data step.
%let quoted="I'd like to";
data temp;
set temp;
quoted=&quoted;
run;