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.
Related
I'm trying to create a folder with the character "&" in the name
I've tried the %superq function which seems to interpret correctly the macro variable
x "cd c:\Users\avibe_000\Dropbox\htdocs\Landa2018\LDPdepartments\";
data _null_;
call symput('mv1','Smith&Jones');
run;
%let testmv1=%superq(mv1);
%put &testmv1 ;
x "mkdir &testmv1";
The code works but creates a folder "Smith" and not "Smith&Jones" as expected
The DCREATE function will create a sub-folder. In macro use nrstr to pass a non-resolved value to the function. %SUPERQ is not, pre se, for value assignment, but for as-is value retrieval based on macro variable name.
filename parent 'C:\Temp';
%put NOTE: parent(path)=%qsysfunc(pathname(parent));
%let rc = %qsysfunc(DCREATE(%nrstr(Smith&Jones),parent));
filename sj 'C:\Temp\Smith&Jones';
%put NOTE: sj(path)=%qsysfunc(pathname(sj));
The macro processor tries to process macro triggers inside of double quotes. But it doesn't inside of single quotes. Also the Windows command line will need quotes around the value with an & in it.
>mkdir a&b
'b' is not recognized as an internal or external command,
operable program or batch file.
>mkdir "a&b"
So change your process to enclose the command in single quotes and the directory name in double quotes.
data _null_;
call symputX('command',quote('mkdir "Smith&Jones"',"'"));
run;
x &command ;
#Tom Nice answer.
And you just been so closed to your own way:
data _null_;
call symput('mv1','d:\Users\avibe_000\Dropbox\htdocs\Landa2018\LDPdepartments\Smith&Jones');
run;
%let testmv1=%str(%")%superq(mv1)%str(%");
%put &testmv1;
x mkdir &testmv1;
You can use the mf_mkdir macro in the SASjs macrocore library - this also uses dcreate() and recursively creates any subdirectories that are needed. It is also delivered as a macro function (so can be used pretty much anywhere in your code).
/* import and compile (or go there and copy paste into your code */
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/base/mf_mkdir.sas";
%inc mc;
/* execute */
%mf_mkdir(/tmp/this %nrstr(&) that)
result:
I'm trying to leverage macro variables (assigned via %LET) for PROC IMPORTS but it's causing the file name to exceed 201 characters.
(ERROR: File Name value exceeds maximum length of 201 characters)
Is there a different way to define variables so that they are passed as values instead of functions?
My old datafile string ends
DATAFILE="...Files_Submitted\201612\Reconcile\Q42016 Principal balances.xls"
I have changed it to
DATAFILE="...Files_Submitted\&yrmm.\Reconcile\&quarter. Principal balances.xls"
Using the following variables.
%LET EOLM= INTNX('MONTH',today(),-&MonthsAgo.);
%LET yrmm= COMPRESS(year(&EOLM.)||PUT(month(&EOLM.),z2.));
%LET qtr = COMPRESS(year(&EOLM.)||COMPRESS('Q'||CEIL(month(&EOLM.)/3)));
Thanks in advance for any/all assistance.
You're not actually passing things the right way here - you're mixing two different things, which is causing your issue.
You can either resolve the results of these functions in your macro variables, OR you can store the function calls and treat this as if you'd typed the functions into the data step - so use CATS or something to combine them.
As it is, you're ending up with a filename like "\\path\to\COMPRESS(INTNX(MONTH...", not with the results of those functions, hence your problem.
So, one option:
DATAFILE= cats("...Files_Submitted\",&yrmm.,"\Reconcile\",&quarter.,"Principal balances.xls";
That would let the functions provide their values as you expect.
The other option is to use %SYSFUNC to ask the values to be resolved in the macro variables. This is the more common way, though certainly neither is really specifically better for all purposes.
%LET EOLM= %sysfunc(INTNX(MONTH,%sysfunc(today()),-&MonthsAgo.));
And similar for the other two. Note that I remove the quotes around MONTH as quotes are not used in %SYSFUNC calls (unless you want to use quote characters themselves, but not as string delimiters).
%LET yrmm= %sysfunc(year(&EOLM.))%sysfunc(month(&EOLM.),z2.);
Note here I put the format in the SYSFUNC call directly; also note that we do not use concatenation characters in macro variables (they just produce text) and typically you don't need to use COMPRESS (though not always).
%LET qtr = %sysfunc(year(&EOLM.))Q%sysfunc(CEIL(%sysevalf(%sysfunc(month(&EOLM.))/3)));
Here we use %SYSEVALF to do the math (normally you can't have noninteger math in macro syntax). We also remove quotes from Q and just place it in line.
Putting it all together:
%let monthsAgo = 3;
%LET EOLM= %sysfunc(INTNX(MONTH,%sysfunc(today()),-&MonthsAgo.));
%put &=EOLM;
EOLM=20789
%LET yrmm= %sysfunc(year(&EOLM.))%sysfunc(month(&EOLM.),z2.);
%put &=yrmm;
YRMM=201612
%LET qtr = %sysfunc(year(&EOLM.))Q%sysfunc(CEIL(%sysevalf(%sysfunc(month(&EOLM.))/3)));
%put &=qtr;
QTR=2016Q4
Of course more easy might have been to use formats for yrmm/qtr...
%let yrmm = %sysfunc(putn(&eolm.,yymmn6.));
%let qtr = %sysfunc(putn(&eolm.,yyq6.));
%put &=yrmm &=qtr;
Or even (and this might be getting a bit cute) removing the %SYSFUNC from &EOLM and letting the %SYSFUNC format option handle the formatting. Note here EOLM does not store a number, but stores the text you see on the screen, and the number doesn't get resolved until YRMM or QTR is defined.
%LET EOLM= INTNX(MONTH,%sysfunc(today()),-&MonthsAgo.);
%let yrmm = %sysfunc(&eolm.,yymmn6.);
%let qtr = %sysfunc(&eolm.,yyq6.);
%put &=yrmm &=qtr;
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|;
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.
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.