I have the following code, I am trying to generate a standalone code from the macro (written to an external sas file). However, by default the complete code is generated and written to the external file. I would like to know if there is a way by which one can control, which parts of the macro are written to the external file. Appreciate all the help I can get on this.
%macro tempmacro(outds=);
/* I dont want this following code to be printed */
proc sql noprint;
SELECT cats(name,"=",substr(name,2))
INTO :renames SEPARATED BY " "
FROM dictionary.columns
WHERE LIBNAME="SASHELP" AND MEMNAME=upcase('BASEBALL');
quit;
/* I only Want this following data step printed to the external file */
data &outds;
set sashelp.baseball;
rename &renames;
run;
%mend;
options mfile mprint;
filename mprint "D:\test_code.sas";
data _null_;
file mprint;
%tempmacro(outds=data1);
options nomfile nomprint;
run;
Set option nomprint before your proc sql and then option mprint afterwards to restore it. For bonus UX points, check what the value of the option was before using %sysfunc(getoption(mprint)) at the start of your macro, and restore it to the same value afterwards.
Related
On our ZOS (mainframe) we have a library called
USER.PGM.WEEKLY
where several sas programs(members) are located
I am trying to retrieve a list of all the member from my PCSAS with following code
rsubmit;
proc source indd='C009BSA.BSA.BIBHLP.SAS' select *; print;run;
endrsubmit;
signoff;
But it errors out with
ERROR 22-322: Syntax error, expecting one of the following: ;, DIRDD, INDD, MAXIOERROR, NOALIAS,
NODATA, NOMEM, NOPRINT, NOSUMMARY, NOTSORTED, NULL, OUTBLK, OUTDD, PAGE, PRINT,
SEARCH.
ERROR 180-322: Statement is not valid or it is used out of proper order.
I have tried to google around to find the solution but haven't been able to sort it out.
How ever i am able to download one member at the time by running
filename inpds 'USER.PGM.WEEKLY' shr;
proc download infile =inpds(PPRINT_TO_PDF)
outfile='L:\Work\PPRINT_TO_PDF';
run;
Try something like this. You might need to use an actual physical file instead of using the TEMP filename engine on ZOS.
filename dirlist temp;
rsubmit;
filename dirlist temp;
proc source indd='C009BSA.BSA.BIBHLP.SAS' dirdd=dirlist; run;
proc download infile=dirlist outfile=dirlist; run;
endrsubmit;
https://v8doc.sas.com/sashtml/os390/z0217440.htm
If you just want to download all of the members of the PDS then PROC DOWNLOAD can do that for you without you needing to have the list of members.
filename outdir '/where/I/want/to/write/';
rsubmit;
filename indir 'C009BSA.BSA.BIBHLP.SAS';
proc download infile=indir(*) outfile=outdir; run;
endrsubmit;
SAS EG
Is there any way I can print the query/statement used to get the output, along with the output, using SAS ODS?
Suppose,
ods pdf file=pdfile;
proc sql;
select a.*
from tab1 a inner join tab2 b
on a.something=b.something
where <>
having <>;
quit;
ods _all_ close;
this would print the OUTPUT generated from the above query. But can I also get the query printed via the ods pdf along with the output?
There's no automatic way to redirect the log that I'm aware of.
There are a few ways to get what you want, however.
First off, if you are able to use Jupytr, SAS has plugins to enable that to work with SAS, and then you can simply write in the notebook and run the code, and the results appear with your code just as you want. See Chris Hemedinger's blog post on the subject for more details.
Second, SAS Studio will support a notebook-style interface probably with the next major revision (I believe version 5.0) which will release late next year. So similarly, you would put your code and get your output in the same windows.
Finally, the third option is to do as Reeza suggested - write to a log file, then print that to the output. It's messy but possible.
Here's an example of the latter. I don't make any effort to clean it up, note, you'd probably want to remove the logging related to PROC PRINTTO and the otehr notes (or turn on NONOTE).
ods pdf file="c:\temp\test.pdf";
filename logfile temp;
proc printto log=logfile;
run;
proc sql;
select * from sashelp.class;
quit;
proc printto;
run;
data _null_;
infile logfile;
input #1 #;
call execute(cats('ods text="',trim(_infile_),'";'));
run;
ods _all_ close;
I'm having basically the same issue as the following post:Proc Data sets argument error- Error 22-322 expecting a name
But the answer didn't solve my problem.
%let _EFIERR_ = 0; /* set the ERROR detection macro variable */
libname indata "E:\el30053_54_55\el30053-postprocessor\output\files";
/* Format HYPO1001 variables */
PROC datasets library=INDATA;
MODIFY INDATA.hypo1001
/* Format section. */
format HYPOR1;
/* Should not need to edit anything below. */
run;
quit;`
Essentially, I have several datasets in the library INDATA. One of them is called hypo1001. Bottom line, I need to rename some of the variables in the dataset, but the rename statement isn't working because there are a few variables with invalid formats. So now I'm trying to fix the formats, but it doesn't seem to be working. From what I can tell, my syntax is correct, but I have very limited experience with SAS that doesn't usually extend much beyond just converting xpt files to SAS format.
I'm getting the following errors in the log:
ERROR 22-322: Expecting a name.
ERROR 201-322: The option is not recognized and will be ignored.
The libref you used to define the library should not be included in the member name that you use in the MODIFY statement. Try this example:
data class; set sashelp.class; run;
proc datasets nolist lib=work;
modify class ;
format name ;
run;
quit;
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'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;