I am looking to include one sas program inside the macro written in another sas program.
So:
sas_prog1.sas:
data test;
a=1;
run;
sas_prog2.sas:
%macro m2;
%include sas_prog1.sas;
%mend;
%m2;
Does the data step in sas_prog1.sas also need to be wrapped inside a macro?
No - you don't need to. When you use an %include statement, it just essentially writes out all contents in the included file at that location. In your case it just dumps the datastep code and hence it effectively becomes:
%macro m2;
data test;
a=1;
run;
%mend;
%m2;
So you should be good to go.
you can include a code in another in writting it to a temp file as character.
filename exec_code temp;
data _null_;
file exec_code;
put ' your sas instruction'
put 'your sas instruction'
run;
and in your macro use an include
%macro mymacro();
%include exec_code;
%mend;
Assuming that sas_prog1.sas is a being used as a module and you will have multiple modules for the entire code, you can simply use a %include to execute the program. There is no need to execute it inside of a macro in sas_prog2, but it can be.
contents of file saved as sas_prog1.sas:
data test;
a=1;
run;
contents of sas_prog2.sas:
%include "[prog_dir]\sas_prog2.sas";
Related
I have a table cust_base with 1000 variables. And I have a text file contents1 containing the names of 250 variables separated by tab, that I actually need to work with. I want to do something similar to:
%include "/location/contents1.txt";
data new_cust_base(keep = &contents1.txt);
set cust_base;
run;
Is this the correct approach/syntax? Or is there a better way to go about it? I tried digging online, but couldn't find much. Thanks a lot.
You can %include source code as the interior of a keep statement.
set …;
KEEP
%include "/location/contents1.txt";
;
Working example:
data _null_;
file 'c:\temp\keeplist.tab';
put 'name' "09"x 'age' "09"x 'weight';
run;
data work.class;
set sashelp.class;
KEEP
%include 'c:\temp\keeplist.tab';
;
run;
Context: We use SAS 9.4 and Enterprise Guide 7.15. Currently, we are implementing some new Macros and of course have to change a lot along the way. Sometimes smaller, sometimes bigger changes. The problem is, that in order to get the changes to work, SAS needs us to manually compile the Macro code or to restart the session which is a bit tedious.
This is the current setup in our main file (which calls all the Macros):
/* Macro options */
MAUTOSOURCE sasautos = "<path to macro>" mlogic mlogicnest mprint mprintnest MRECALL
Is it possible, while using the MAUTOSOURCE */ sasautos ="" option, to tell SAS every time the Macro is called to actually also compile the Macro instead of using the session-stored Macro? Ideally, the Macro would only be compiled when the whole line of code from the main file (MAUTOSOURCE */ sasautos ="" etc.) is executed, otherwise it should keep a compiled version in the session.
I found this paper (The Autocall Macro Facility in the SAS for Windows Environment) which states in the conclusion
After that SAS will use the code that has already
been compiled. If changes are made to the macro, it must be
compiled again before the changes go into effect.
which I hope doesn't mean that I have to do it manually. Is there any Macro option to set?
In SAS 9.3 they added the %SYSMACDELETE macro function. So if you just want to let autocall redefine a single macro then use that to remove the current definition.
%symacdelete mymacro;
Here is a utility macro that will query SASHELP.VCATALG view to find compiled macros in the WORK library and delete them. It has options to either list names of macros to delete or keep. Note that normal SAS sessions use WORK.SASMACR to store the compiled macros. But SAS/Studio and EG (and perhaps other ways of running SAS) use WORK.SASMAC1 instead.
https://github.com/sasutils/macros/blob/master/macdelete.sas
%macro macdelete(delete,keep);
/*----------------------------------------------------------------------------
Remove compiled macros using %SYSMACDELETE macro statement.
Use DELETE parameter to list macro names to delete.
Use KEEP parameter to list macro names to NOT delete.
Calling it with no values will delete all macros not currently running.
----------------------------------------------------------------------------*/
%local libname memname objname objtype fid i;
%do i=1 %to %sysmexecdepth;
%let keep=%sysmexecname(&i) &keep;
%end;
%if %length(&delete) %then %let delete=and findw("&delete",objname,',','sit');
%let fid=%sysfunc(open( sashelp.vcatalg(keep=libname memname objname objtype
where=(libname='WORK' and objtype='MACRO' and memname like 'SASMAC_'
and not findw("&keep",objname,',','sit') &delete))));
%if (&fid) %then %do;
%syscall set(fid);
%do %while(0=%sysfunc(fetch(&fid)));
%put %sysfunc(compbl(Removing &objname from &libname catalog &memname));
%sysmacdelete &objname;
%end;
%let fid=%sysfunc(close(&fid));
%end;
%else %put %qsysfunc(sysmsg());
%mend macdelete;
Example:
3348 %macro test1; %mend;
3349 %macro test2; %mend;
3350 %macro test3; %mend;
3351 %macro test4; %mend;
3352 %macdelete(test1 test3);
Removing TEST1 from WORK catalog SASMACR
Removing TEST3 from WORK catalog SASMACR
3353 %macdelete(keep=test2);
Removing TEST4 from WORK catalog SASMACR
Example when running SAS/Studio or Enterprise Guide:
97 %macro test1; %mend;
98 %macro test2; %mend;
99 %macro test3; %mend;
100 %macro test4; %mend;
101 %macdelete(test1 test3);
Removing TEST1 from WORK catalog SASMAC1
Removing TEST3 from WORK catalog SASMAC1
If you delete the compiled macro from WORK.SASMACR SAS will have to re-compile the macro when you call it again.
proc catalog c=work.sasmacr;
*contents;
delete your-macro-to-recompile.macro;
run;
quit;
For your normal users you should set a release schedule of changes. The users will know they need to re-start a new session after the release is made.
For your developers that are testing the changes as they are made they just need to use %INCLUDE to re-compile the macro. So if you know that macro XYZ is changed then just run:
%include maclib('xyz.sas');
Or you could brute force it and recompile all of the macros in your autocall library.
%incldue maclib('*.sas');
You could get fancier and make a macro that cleans out the actual catalog of compiled macros. Something like:
%macro clean_autocall;
proc catalog force c=work.sasmacr;
save clean_autocall /et=macro;
quit;
options mrecall mautosource;
%mend clean_autocall;
But if you are using Enterprise Guide there are two issues.
First for some reason it uses a different catalog to store the compiled macros. (Why?) I think it is WORK.SASMAC1 instead of WORK.SASMACR.
Second EG will manually compile a bunch of helper macros that it needs. I am not sure if there is an official source for the complete list of these macros? You could try adding code to your project to automatically create the list based on what entries are in the catalog when your project starts. Here is a list I made 10+ years ago when I tried using EG with production environment. But I am sure that it is out of date.
%let s_eg_save= checkfmt checkhotfix
eclibassign eclibunassign enterpriseguide gaccessible
_eg_conditional_dropds
;
I know we can use data steps to infile "*.sas" programs as a dataset, each line of code is one record of the dataset. And then I can make changes to the dataset with SAS.
Let's say I have 100 programs already exist in c:\pgm, all I need to do is minor modifications, such as changing flag1 to flag2 for all these 100 program. If I don't want to open each program and substitute the flag one by one. Is there a way to get all program names in c:\pgm, so that I can loop over these names and do the substitution.
I'm using SAS 9.4 and EG. Thanks!
I might have overcomplicated it a bit, but the idea is the following:
extract the list of all the sas files in folder
create a macro variable array of these files
make a loop and call editFile macro on each single extracted file.
%let dir=C:\prg;
%let var_prefix=vars_;
%macro batchEdit;
data _null_;
pfad="&dir.";
rc=filename("fileref",pfad);
did=dopen("fileref");
if did eq 0 then do;
putlog "Dir does not exist";
return;
end;
num=dnum(did);
j=1;
do i=1 to dnum(did);
name=dread(did,i);
if UPCASE(substrn(name,max(1,length(name)-3),4)) eq ".SAS" then do;
call symput(CATS("&var_prefix.",j),name);
j=j+1;
end;
end;
call symput("varcnt",j);
rc=dclose(did);
rc=filename("fileref");
run;
%DO j=1 %TO &varcnt.;
%editFile(file=&&&var_prefix&j);
%END;
%mend;
%macro editFile(file=);
%put In this Macro you should define what modification should be done on each single file;
%put Current File: &file.;
%mend;
%batchEdit;
P.S. I have tested it on unix SAS server, so it might require some modifications on PC SAS.
Assume I have this program:
1 data temp;
2 set _null_;
3 run;
4
5 %put Hello world;
and I want to add two lines to it, one that runs lines 1-3 of the program, and another that runs line 5.
The second example here suggests that %include may be what I'm looking for, but %include 1-3 and %include 5 do not work. %include [path] 1-3 gets me into an infinite loop, which is undesirable.
What is the best way to accomplish this? Thanks!
EDIT: this only works for lines that have previously been submitted.
You need SPOOL option. I used RESETLINE statement to reset the line number useful when using SAS/EG. I would like to know how you intend to use it.
options spool=1;
resetline;
data temp;
set _null_;
run;
%put Hello world;
%include 1-3 / source2;
%include 5 / source2;
Macros, perhaps?
%macro one(datasetname= );
data &datasetname;
set _null_;
run;
%mend one;
%macro two(textstring= );
%put &textstring;
%mend two;
%one(datasetname= temp1);
%two(textstring= Hello world);
%one(datasetname= temp2);
%two(textstring= Hello new world);
You could feed macro variables into the process from a dataset rather than multiple macro calls. See the examples starting on p 11 here:
First & Ronk, SGI 130-30, SAS® Macro Variables and Simple Macro Programs
A single extra return could screw up your line references...If you need to add a header, or a new line of code somewhere or something are you willing to go back and fix all your line number references?
I recommend using the macro or %include options.
%macro repeat_code();
***sas code goes here;
%mend;
%repeat_code
For the %include you can create the lines inside a new file and then reference them in your code with a %include.
Depending on what those lines are actually doing, you may have other options. For example if it's a lookup or re-coding a variable I would use a format instead.
I'm trying to use a Macro that retrieves a single value from a CSV file. I've written a MACRO that works perfectly fine if there is only 1 CSV file, but does not deliver the expected results when I have to run it against more than one file. If there is more than one file it returns the value of the last file in each iteration.
%macro reporting_import( full_file_route );
%PUT The Source file route is: &full_file_route;
%PUT ##############################################################;
PROC IMPORT datafile = "&full_file_route"
out = file_indicator_tmp
dbms = csv
replace;
datarow = 3;
RUN;
data file_indicator_tmp (KEEP= lbl);
set file_indicator_tmp;
if _N_ = 1;
lbl = "_410 - ACCOUNTS"n;
run;
proc sql noprint ;
select lbl
into :file_indicator
from file_indicator_tmp;
quit;
%PUT The Source Reporting period states: &file_indicator;
%PUT ##############################################################;
%mend;
This is where I execute the Macro. Each excel file's full route exists as a seperate record in a dataset called "HELPERS.RAW_WAITLIST".
data _NULL_;
set HELPERS.RAW_WAITLIST;
call execute('%reporting_import('||filename||')');
run;
In the one example I just ran, The one file contains 01-JUN-2015 and the other 02-JUN-2015. But what the code returns in the LOG file is:
The Source file route is: <route...>\FOO1.csv
##############################################################
The Source Reporting period states: Reporting Date:02-JUN-2015
##############################################################
The Source file route is: <route...>\FOO2.csv
##############################################################
The Source Reporting period states: Reporting Date:02-JUN-2015
##############################################################
Does anybody understand why this is happening? Or is there perhaps a better way to solve this?
UPDATE:
If I remove the code from the MACRO and run it manually for each input file, It works perfectly. So it must have something to do with the MACRO overwriting values.
CALL EXECUTE has tricky timing issues. When it invokes a macro, if that macro generates macro variables from data set variables, it's a good idea to wrap the macro call in %NRSTR(). That way call execute generates the macro call, but doesn't actually execute the macro. So try changing your call execute statement to:
call execute('%nrstr(%%)reporting_import('||filename||')');
I posted a much longer explanation here.
I'm not too clear on the connections between your files. But instead of importing the CSV files and then searching for your string, couldn't you use a pipe command to save the results of a grep search on your CSV files to a dataset and then read just in the results?
Update:
I tried replicating your issue locally and it works for me if I set file_indicator with a call symput as below instead of your into :file_indicator:
data file_indicator_tmp (KEEP= lbl);
set file_indicator_tmp;
if _N_ = 1;
lbl = "_410 - ACCOUNTS"n;
data _null_ ;
set file_indicator_tmp ;
if _n_=1 then call symput('file_indicator',lbl) ;
run;