We have hundreds of macros across several autocall libraries that I'd like to compile using the MSTORE facility.
The problem is that some of those libraries are used on other sites - and we don't want to add /STORE to every definition, as then it is required to use the MSTORED option.
I guess the answer is to add /* /STORE */ at the end of every %macro statement, so that the .sas program files can be updated with a regular expression (or some other text replacement technique) on every redeploy, but this seems like a messy workaround.
Is it possible / in some way supported, to compile regular (unmodified) autocall macros and store the definitions? Eg by %inc'ing each one and saving the work macro catalog, or something like that?
I won't say definitively that this isn't possible, but I can report that I tried to do the same thing some time ago and got stuck on the same point. I was also unable to find any way of doing this other than adding /store to every %macro statement.
I vaguely remember that I was able to upload the work.sasmacr catalogue from one session to another on the same machine (after first compiling a few autocall macros to populate it), but the other session didn't recognise the macro definitions from transferred catalogue even though the appropriate options were set for using stored compiled macros.
My motivation was different from yours - I was looking for a way to define a macro in one session and execute it in another without saving it in an autocall folder or %including it in both sessions - but the conclusion was the same.
What is the problem that you had?
First compile all of your autocall macros. Say you have a fileref named MYMACS that points to the directory with the source code.
%include mymacs(macro1,macro2,.....);
You might use a program to search for all of the source files so that you could automate generating the %include statement(s). Or you could use a datastep and copy all of the source files into a single temporary file and include that.
filename src temp;
data _null_;
infile "&inpath\*.sas" ;
file src ;
input;
put _infile_;
run;
%inc src ;
Then copy the WORK catalog to you a new location. Note that the name is different if you are running SAS on an application server. In that case try copying from WORK.SASMAC1 instead of WORK.SASMACR.
libname out base "&path";
proc catalog cat=work.sasmacr et=macro ;
copy out=out.sasmacr ;
run;
quit;
You can test if it worked by clearing your current work macro catalog, so you know SAS is not finding the macro there, and setting options to point to the new catalog of compiled macros.
proc catalog cat=work.sasmacr kill force ;
quit;
options mrecall mstored sasmstore=out ;
Then trying to run one of the copied compiled macros.
Now start up a new session and try using the compiled macros in that session.
Here was the approach I took for compilation (of course there are many alternative ways). The locations to query can be extracted from:
%put %sysfunc(getoption(sasautos));
The approach relies on macros being closed off with )/*/STORE SOURCE*/; as follows:
%macro some_macro(var1=x, var2=y
)/*/STORE SOURCE*/;
The SAS code has to be a program, as you can't create a stored compiled macro from within a macro.
/* set mstore options */
options mstored sasmstore=yourlib;
/* get list of macros */
/* taken from macrocore library */
/*https://github.com/sasjs/core/blob/main/base/mp_dirlist.sas*/
%mp_dirlist(path=/location/one,outds=in1)
%mp_dirlist(path=/location/two,outds=in2)
/* set up a temporary fileref */
filename tmp temp;
/**
* write each source macro to the fileref
* and switch on the STORE option
*/
data want;
set in1 in2;
infile dummy filevar=filepath end=last ;
file tmp;
do until(last);
file tmp;
input;
if _infile_=')/*/STORE SOURCE*/;' then put ')/STORE SOURCE;';
else put _infile_;
end;
run;
%inc tmp; /* compile macros */
filename tmp clear; /* clear ref */
Related
I have a program that starts with:
%let filename = file1.csv
The program then imports the file into a sas dataset and then moves some of the data to a sql table based on some rules.
I want to have the program loop through processing whatever files are in a folder..I guess by redefining filename each time it gets to the end of the program and going back to the top until all of the csv files in the folder have been processed?
This is an example of a process that might benefit from creating a macro. Sounds like your existing code is close to being ready to become a macro. Just wrap the existing code into a macro definition that takes FILENAME as a parameter (remove your %let statement).
Then your existing program can become something like this. Where the last line is the one that actually runs the steps defined in the macro definition.
%macro loadfile(filename=);
... existing code ....
%mend loadfile;
%loadfile(filename=file1.csv);
To extend it to loading all files in a directory you just need to generate the list of files and use the list to generate a series of calls to the macro. So something like this might work on a Windows machine. So it will call the Windows command DIR to get the list of files and read the result into a variable and for each file found generate a call to the macro. The commands pushed by CALL EXECUTE will run after the data step finishes.
data _null_;
infile 'dir /b *.csv' pipe truncover ;
input filename $256. ;
call execute(cats('%nrstr(loadfile)(filename=',filename,')'));
run;
Say I have defined a macro function, and for some reason (e.g. a mistake), I deleted the code. I still have the macro though.
How can I retrieve the code I used to define it ?
To define the macro, I just executed:
%macro(param1,param2);
my code
%mend;
If you defined the macro with the source option specified and in a library you can retrieve it with the following:
%copy MACRO_NAME / source;
Official SAS answer, see original link below:
There is no way to retrieve the original source code from a stored compiled macro. You must always save the original code in another file so that you can modify it later.
Starting with SASĀ® 9.1, there is a new SOURCE option for the %MACRO statement. When used with the existing STORE option, the SOURCE option combines and stores the source of the compiled macro.
The compiled macro code becomes an entry in a SAS catalog in a permanent SAS data library. The compiled macro and the source code are stored together in the same SASMACR catalog. The SOURCE option requires that the STORE option and the SAS option MSTORED be set. You can use the SAS option SASMSTORE= to identify a permanent SAS data library. You can store a macro or call a stored compiled macro only when the SAS option MSTORED is in effect.
Note: The source code that is saved by the SOURCE option begins with the %MACRO keyword and ends with a semicolon following the %MEND statement. Now that you have a way to store the source code with the SOURCE option, you also need a way to retrieve this information. The answer is the new %COPY statement, which copies specified items from a SAS macro library.
For example:
libname test 'c:\';
options mstored sasmstore=test;
%macro test(arg) / store source des="test of the source option";
%put arg = &arg;
data one;
x=1;
run;
%mend test;
%copy test / source;
Source:
http://support.sas.com/kb/22/352.html
If you don't have the SOURCE option and your macro is relatively basic you could try using the MPRINT and SYMBOLGEN to get the log with the code but if you have conditional logic it'll be hard to recreate the code for sure.
options mprint symbolgen;
%my_macro(param1, param2);
Based on this post, I can get a list of compiled macros in a program, ordered by their first appearance, using the following code:
%macro FirstMacro();
%put Hello, World!;
%mend;
%macro SecondMacro();
%put Goodbye, cruel world!;
%mend;
proc sql;
select objname, created
from dictionary.catalogs
where objtype='MACRO' and libname ^= 'SASHELP'
order by created
;
quit;
This, however, gives all the macros in uppercase.
Object Name Date Created
--------------------------------------------------
FIRSTMACRO 09FEB17:16:12:31
SECONDMACRO 09FEB17:16:12:31
I use PascalCase for my macro names (as illustrated above). Is there a way to get a similar list, but with case sensitivity preserved?
It seems like PROC SCAPROC might provide such a list, but it's unclear to me. There is a note from when PROC SCAPROC was first released (in 9.2) that indicates that PROC SCAPROC doesn't provide support for when a %MACRO is used. However, the note says this will be a feature in future versions of SAS. With SAS 9.4 it's unclear whether the feature has been added yet.
The only other alternative I can think of is to write a script in some other language which analyzes the text above for the macro names.
So, it's feasible this may give you what you want, at least with the above example.
It does not expand the macro when it's run - meaning, you don't get to see what happens inside the macro, as it runs. This may be a problem if you are generating macros at runtime, i.e. using your data to create macros - like
data _null_;
set something;
call execute ('%macro ',name_var,'; ... ; %mend;');
run;
That may not show up properly. But if you're just writing macros in code and compiling them, then yes, you might be able to use this - though you have to run the compilation step at least as part of this (this doesn't analyse plaintext, it analyzes a job while it runs).
So if I do something like this:
proc scaproc;
record 'c:\temp\recording.txt' expandmacros;
run;
%macro PascalCaseMacro();
%put PascalCaseMacro;
%mend;
%PascalCaseMacro;
proc scaproc;
write;
run;
I get output like this:
/* JOBSPLIT: PROCNAME DATASTEP */
/* A bunch of lines like that with JOBSPLIT at the start */
%macro PascalCaseMacro();
%put PascalCaseMacro;
%mend;
%PascalCaseMacro;
/* Some more JOBSPLIT lines */
You could then parse that text file for lines that started with %macro.
I do think the technical answer to your question is "no", they have not implemented this: while it does update the technical information with some accesses of the macro catalog, it doesn't ever include in the detail of what macro was run (in the /* JOBSPLIT */ lines). That's really what that technical note is talking about, I think; i.e., this is not perfectly useful for optimizing macro code. The actual code that generates the macro (which I think is all you need) is there. (However, if you have a stored-compiled-macro, that would not be available.)
I was able to adapt the Windows Batch Script from this post to get a list of case sensitive macros defined in a given program.
REM GetListOfDefinedMacros.bat
#ECHO OFF
SET /P SASFILE= Enter SAS filepath:
SET SASFILE=%SASFILE:"=%
ECHO.
ECHO The defined macros are:
ECHO.
FOR /F "tokens=*" %%A IN ('FINDSTR "macro" "%SASFILE%" ^| FINDSTR "("') DO CALL :FindString "%%A"
ECHO.
pause
GOTO :eof
:FindString
SET String=%~1
SET String=%String:*macro =%
SET String=%String:)=X%
SET String=%String:(=`%
FOR /F "tokens=1 delims=`" %%A IN ('ECHO.%String%') DO ECHO.%%A
GOTO :eof
The program prompts the user for the SAS program's full file path. It then parses the code, looking for lines which contain both "macro" and "(", returning the any characters that lie between them.
The following assumptions must hold:
each line contains a single programming statement; "macro" and "(" must be on the same line,
a macro is defined using parentheses; it will not work for %macro MacroName; style definitions.
Note that the script doesn't list compiled macros. It can only detect macros which have definitions within the given program. Macros compiled through %include or AUTOCALL will not show up.
For example,
/*C:\temp\sample sas program.sas*/
%macro FirstMacro();
%put NOTE: Hello, world!;
%mend;
%macro SecondMacro();
%put ERROR: Goodbye, cruel world!;
%mend;
%macro ThirdMacro(input);
%if &input. = hi %then %FirstMacro();
%else %SecondMacro();
%mend;
%ThirdMacro(hi);
%ThirdMacro(bye);
This gives the following output to the command prompt:
Enter SAS filepath: "C:\temp\sample sas program.sas"
The defined macros are:
FirstMacro
SecondMacro
ThirdMacro
Press any key to continue . . .
From there, you can copy and paste the list. Or, the code could be modified to redirect the list to a file.
Here is a native SAS solution which should rely only on BASE capabilities. It is intended for use in the Enhanced Editor on Windows.
%macro ListMacroDefinitions();
/*Get file-path of current program's source code.*/
proc sql noprint;
select distinct xpath
into : _SASProgramPath trimmed
from dictionary.extfiles
where xpath contains "%sysget(SAS_EXECFILENAME)"
;
quit;
/*Read in source code, parse for macro names, and
output to log.*/
options nonotes;
data _null_;
infile "&_SASProgramPath." dlm = '```';
length
line $ 256
macro_name $ 32
;
input line;
if line =: '%macro' then do;
indexLeftParenthesis = index(line, '(');
macroNameLength = indexLeftParenthesis - 8;
macro_name = substr(line, 8, macroNameLength);
put macro_name;
end;
run;
options notes;
dm 'wpgm';
%mend;
It works by first determining what the current program's name is using %sysget(SAS_EXECFILENAME). The %SYSGET function returns the value of the environment variable SAS_EXECFILENAME which
specifies the filename of the current SAS source file, if it is
submitted from the enhanced editor.
It then looks in the DICTIONARY.EXTFILES table and extracts a file path associated with name of the current source file (i.e. the program currently being edited). This file path is assigned to a macro variable.
Next, a data step reads in the source file of the current program. It reads the file line by line setting a non-existent delimiter (three back ticks) in the INFILE statement.
By default, if the INPUT statement tries to read past the end of the
current input data record, then it moves the input pointer to column 1
of the next record to read the remaining values.
Defining the delimiter to a string which never appears in the source code causes the line read in to never be parsed and thus be read in whole. Each line is checked for whether it contains a macro definition (via the =: operator). Specifically, it looks for the string '%macro'. We then manually parse each definition line for the characters between the '%macro ' statement and the first parenthesis. The result is output to the log. During this process, notes are disabled. Finally, the windowing command wpgm is issued. When the macro names are written to the log, the log window is selected. The wpgm command returns focus to the last program window, which should be the program currently being edited.
The whole process is put inside a macro. If you place the macro in an AUTOCALL library (found by issuing %put %sysfunc(pathname(sasautos));), you can get a list of all the macros defined within your current program. Note that for this list to be complete, you must save your current program before running the macro.
I've downloaded a sas dataset and a format catalog that goes with it. This is probably super basic but I can't seem to set up the libraries so that I can make use of the formats, and I can't open the dataset unless I use the NOFMTERR option. They are both in the same windows folder. Please help.
The following code should explain how to add a library (in this case the library mylib) to the FMTSEARCH option which dictates which libraries are searched for SAS formats:
/* Display the current fmtsearch option - librefs searched in order for formats */
%put %sysfunc(getoption(fmtsearch));
libname mylib 'windows-folder';
/* Append the library containing the format catalog */
options append=(fmtsearch=mylib);
/* Check the fmtsearch option again */
%put %sysfunc(getoption(fmtsearch));
Just point SAS to the library where your format catalog is, and this should resolve the format errors and allow you to display the formatted data.
For users of 9.1.3, you can directly alter the fmtsearch option. Here's a method of doing so most similar to #mjsqu's code above (which preserves the already-existing format options) and appends to the end.
* Store fmtsearch option value in macro variable;
%let fmtsearch=%sysfunc(getoption(fmtsearch));
*Append NEWLIB to the end (assuming NEWLIB is your library name);
*Uses SUBSTR to strip off the end parenthesis;
%let fmtsearch_new = %substr(&fmtsearch,1,%length(&fmtsearch.)-1) NEWLIB);
*Check new value;
%put &fmtsearch_new;
*Set fmtsearch option to new value;
options fmtsearch=&fmtsearch_new.;
*Check that option was set;
%put %sysfunc(getoption(fmtsearch));
Of course, this will re-append the value multiple times if you run this multiple times; that's not harmful, but might look odd. You could do some additional checking to see if it is already in the string and not re-add if it is.
I have 4 txt files that need to be loaded to SAS and save them as 4 sas files. Here are how the text files look like: cle20130805.txt, cle20130812.txt, cle20130819.txt and cle20130826.txt . I used a % Do loop under % Macro in order to get the 4 files imported with only one invoke of the Macro. So Here is my code:
%macro cle;
%do i=20130805 %to 20130826 %by 7;
Data cleaug.cle&i;
infile "home/abc/cle&i..txt" dlm= '|' dsd firstobs=1 obs=100;
input a_no b_no c_no;
run;
%end;
%mend cle;
%cle
I am expect to have 4 sas file saved with only invoke the marco once. However it just can't run successfully. Any ideas where am I doing wrong in the code?
Thanks,
I don't recommend you try to write one macro to import all four files. Either it will be a specific macro you only ever use once - in which case you could just write this by hand and save the time you've already spent - or it will be something you have to modify every single month or whatever that you use it.
Instead, make the macro something that does precisely one file, but includes the information needed to call it easily. In this case, it sounds like you need one parameter: the date, so 20130805 or whatnot. Then give it a reasonable name that really says what it does.
%macro import_files(date=);
Data cleaug.cle&date.;
infile "home/abc/cle&date..txt" dlm= '|' dsd firstobs=1 obs=100;
input a_no b_no c_no;
run;
%mend import_files;
Now you call it:
%import_files(date=20130805)
%import_files(date=20130812)
%import_files(date=20130819)
%import_files(date=20130826)
Just as easy as the macro you wrote above, even hardcoding the four dates. If the dates are predictable in some fashion, you can generate the macro calls very easily as well (if there are more than 4, for example). You could do a directory listing of the location where the files are, or call the macro from a data step using CALL EXECUTE if you really like looping.