Syslput, rsubmit & macro - sas

I am trying to pass a local macro variable within a macro to a remote session as follows (this example assumes 'mynode has already been signed on to):
%macro mytest;
%do i = 1 %to 3;
%syslput mynewval = &i;
rsubmit mynode;
%let mynewval2 = &mynewval;
%put &mynewval2;
endrsubmit;
This looks like the correct syntax to me, however '&mynewval2' is resolving to blank when I attempt to print it to the log. Can anyone see what I am doing wrong?
Thanks
%end;
%mend;
%mytest;

The %let mynewval2 = &mynewval; is being run on the client and not the server. IE, the local macro processor is running the code. It doesn't know what &mynewval is -- you defined it with the remote system.
Try wrapping the code inside the RSUBMIT in a macro. I don't have SAS/CONNECT licensed so I cannot test.
rsubmit mynode;
%macro run_on_server();
%let mynewval2 = &mynewval;
%put &mynewval2;
%mend;
%run_on_server();
endrsubmit;

Related

SAS Macro Statement %If and %Let

I am trying to solve a problem in which, based on certain conditions, it assigns you a parameter with the let function. For this exercise I am using %if with %let conditions on the code. The code I have written so far in simplified way is the following:
%let anio = 2022;
%let base = 2;
%Macro Data;
%if &anio = 2022 %then %do;
%Let year_add = %Str(&Base.C);
%Let year_add1 = %Str(&Base.B);
%mend;
%Data;
%put &=year_add;
%put &=year_add1;
The problem is that apparently the macro is not assigning any value to me in the second let statement
The first %put = &year_add gives me the correct result 2C.
Unfortunately with the second %put = &year_add1 it appears the following message: apparent symbolic referenc yeard_add1 not resolved
Can anyone can give me a hand or advise on how I can assign different let statements based on a condition?
Thanks in advance.
Your macro definition is missing an %END for the %DO.
%macro data;
%if &anio = 2022 %then %do;
%let year_add = &Base.C;
%let year_add1 = &Base.B;
%end;
%mend;
If the target macro variables, YEAR_ADD and YEAR_ADD1 do not already exist then your macro will create them as LOCAL to the DATA macro. So once the macro finishes they will be removed.
The easiest solution is just to make sure the macro variables exist before you call the macro.
%let anio = 2022;
%let base = 2;
%let year_add=;
%let year_add1=;
%data;
%put &=year_add;
%put &=year_add1;
If you are certain the macro variables do not already exist in some other macro that is calling %DATA() then you could add a %GLOBAL statement to define them in the GLOBAL macro scope so they will not be removed when the macro finishes by adding this to the macro definition:
%global year_add year_add1 ;
But that will generate an error if they have been defined as LOCAL to some other macro that called %DATA. So to be safe only force them into the GLOBAL scope if they do not already exist.
%if not %symexist(year_add) %then %global year_add;
%if not %symexist(year_add1) %then %global year_add1;
But the logic does not require you to define a macro. Just use the %IF/%THEN/%DO/%END block in open code. Then you won't have any macro variable scoping issues.
%if &anio = 2022 %then %do;
%let year_add = &Base.C;
%let year_add1 = &Base.B;
%end;
That works fine unless you are running on some really old version of SAS.

Rename a external file in a SAS macro

I am getting a generic 'Statement not valid or out of order' message with the below:
%macro test;
data _null_;
%if %sysfunc(fileexist("C:\report_201809.xlsx")) = 1 %then %do;
rc=%sysfunc(rename("C:\report_201809.xlsx",
"C:\report_201809.xlsx"_old.xlsx",'file'));
%end;
%mend;
%test;
The code below should get you what you need. While you can use %if statements in a data step you generally won't need to. I'm guessing the error is coming from the %sysfunc function around the fileexist and rename functions. %sysfunc allows you to call data step functions outside of a data step so it is not needed here.
%macro test;
data _null_;
if fileexist("C:\file.txt") then do;
rc = rename("C:\file.txt", "C:\file2.txt", 'file');
end;
run;
%mend;
Alternatively, you could use an X Command that allows you to execute Windows commands. You could replace the rename function with the following statement.
x move C:\file.txt C:\file2.txt;
Remove the DATA _NULL_ or proceed per #J_Lard.
Macro arguments used in %sysfunc invoked function calls are implicitly quoted and do not need additional ' or "
%macro test;
%local rc;
%if %sysfunc(fileexist(C:\report_201809.xlsx)) = 1 %then %do;
%let rc = %sysfunc(rename(C:\report_201809.xlsx,C:\report_201809_old.xlsx,file));
%end;
%test;
You original code may have worked (by way of non-obvious side effect) if the filename "C:\report_201809.xlsx"_old.xlsx" (having an extraneous ") was corrected to "C:\report_201809_old.xlsx"

Echo SAS macro invocation to SAS log

Is there a system option or similar that will automatically echo macro invocations to the SAS log? When debugging code, I would like to see in the log every macro invocation, including what parameters were passed.
So that if I submit %Test(x=1) the log will show something like:
MACRO INVOKED: %TEST(x=1)
When calling a macro in open code, this is not an issue, because the macro call is shown in the usual log. But when outer macros call inner macros, the actual call to %inner is not shown by default. I tried turning on MLOGIC, MPRINT, etc, but couldn't find something that would show me the macro call itself. I think what I want is an MINVOCATION option.
Below I fake an MINVOCATION option by adding /parmbuff to macro definitions, but was hoping for a way to see macro calls without mucking with the macro definition.
%macro test(x=0,y=0,debug=0) /parmbuff ;
%if &debug %then %put MINVOCATION: %nrstr(%%)&sysmacroname&syspbuff ;
data _null_ ;
x=&x ;
y=&y ;
put x= y= ;
run ;
%mend test ;
%macro outer(debug=0) /parmbuff ;
%if &debug %then %put MINVOCATION: %nrstr(%%)&sysmacroname&syspbuff ;
%test(x=1,debug=&debug)
%test(x=1,y=2,debug=&debug)
%mend outer ;
options mprint mprintnest ;
%outer(debug=1)
Returns the desired:
908 options mprint mprintnest ;
909 %outer(debug=1)
MINVOCATION: %OUTER(debug=1)
MINVOCATION: %TEST(x=1,debug=1)
MPRINT(OUTER.TEST): data _null_ ;
MPRINT(OUTER.TEST): x=1 ;
MPRINT(OUTER.TEST): y=0 ;
MPRINT(OUTER.TEST): put x= y= ;
MPRINT(OUTER.TEST): run ;
x=1 y=0
MINVOCATION: %TEST(x=1,y=2,debug=1)
MPRINT(OUTER.TEST): data _null_ ;
MPRINT(OUTER.TEST): x=1 ;
MPRINT(OUTER.TEST): y=2 ;
MPRINT(OUTER.TEST): put x= y= ;
MPRINT(OUTER.TEST): run ;
x=1 y=2
I think you may be looking for option mlogic.
Example code:
option mprint mlogic ;
%macro y(blah);
%put &blah;
%mend;
%macro x();
%y(hello);
%put x;
%mend;
%x;
Gives:
MLOGIC(X): Beginning execution.
MLOGIC(Y): Beginning execution.
MLOGIC(Y): Parameter BLAH has value hello
MLOGIC(Y): %PUT &blah
hello
MLOGIC(Y): Ending execution.
MPRINT(X): ;
MLOGIC(X): %PUT x
x
MLOGIC(X): Ending execution.
You can see it tells you when the macro begins execution, which macro is executing, and also the value of any paramters passed in.
UPDATE
Based on your clarifications, this was the closest I could find. Basically you need to setup a libname for 'stored' macros. When you define your macro, add the options / store source to tell it to store the source code for the macro into the stored macro library:
libname mac "e:\temp";
option mstored sasmstore=mac;
%macro blah(something=whatever) / store source;
%put hi;
%mend;
You can later retrieve the source code by using the %copy macro (SAS v9+). This macro has options to write the source to a file rather than the log. You can then read in the file and extract the default parameter values yourself.
%COPY blah / source;
Gives:
%macro blah(something=whatever) / store source;
%put hi;
%mend;
This whitepaper goes into additional details.
It's a lot of extra steps I know but that seems to be a pretty unusual request.
You may be better off rethinking your strategy. For example, a far simpler method might simply be to define your defaults this way:
%macro hasDefaults(x=1,y=2);
%local default_x default_y;
%let default_x = 1;
%let default_y = 2;
%if &x ne &default_x %then %do;
%put The default for x was changed from &default_x to &x.;
%end;
%mend;
This is far from ideal as well, but you'll have to weigh up what will work better for your needs.
If you're willing to update all of your macros, which it sounds like you'd have to do anyways, then what about adding:
%put _local_;
at the top of each? At macro invocation, the only local macro variables defined will be those parameters, right?
%macro mymacro(x=,y=,z=);
%put _local_;
proc print data=sashelp.class;
run;
%mend mymacro;
%mymacro(x=1,y=2);
Gives a log of:
08 %mymacro(x=1,y=2);
MYMACRO X 1
MYMACRO Y 2
MYMACRO Z
NOTE: There were 19 observations read from the data set SASHELP.CLASS.
NOTE: PROCEDURE PRINT used (Total process time):
real time 0.05 seconds
cpu time 0.03 seconds
You could always also put the macro name in there:
%macro mymacro(x=,y=,z=);
%put MACRO INVOKED: &sysmacroname;
%put Parameters:;
%put _local_;
proc print data=sashelp.class;
run;
%mend mymacro;
%mymacro(x=1,y=2);
Though it's returned as part of the %put _local_ so it's probably extraneous.

SAS - create macro-name from variable

I am wondering how I can set a macro-name from a variable.
Like this:
%Macro test(name);
...
%Macro new_&name;
...
%Mend;
...
%Mend test
Or if this is not possible:
%macro one(name);
%let mname=&name;
%mend one;
%macro two_&name;
...
%mend;
Any ideas? Many thanks.
First thing that pops into my mind is to use a temporary fileref to build your macros. Then include that fileref.
I think this does what you are looking for:
%macro test(x,file);
data _null_;
file &file;
format outStr $2000.;
outStr = ('%macro test_' || strip("&x") || "();");
put outStr;
put '%put hello world;';
outStr = '%put Passed in value is x:' || "&x and file: &file;";
put outStr;
put "proc means data=sashelp.class(obs=&x) mean; var age; run;";
put '%mend;';
run;
%include &file;
%mend;
filename tempfile temp;
%test(2,tempfile);
%test_2;
filename tempfile clear;
Yes you can do such a thing:
%macro macroFunc();
%put hi there;
%mend;
%macro macroCall(macroName);
%&macroName.();
%mend;
%mcr2(macroFunc);
But I'm really curious in what context this makes sense.
Seems like it will in no time result into a coding mess.
I never knew that you could not use a variable in a %MACRO statement...but that appears to be the case. As it says in the SAS documentation (http://support.sas.com/documentation/cdl/en/mcrolref/61885/HTML/default/viewer.htm#macro-stmt.htm) "you cannot use a text expression to generate a macro name in a %MACRO statement."
My next thought was that you might be able to create the %MACRO statement as a variable, but I couldn't find a way to mask %MACRO in the creation of the variable.
I finally figured out a work around, but it is likely not the best way to do this (and it may not work for what you're trying to do). I found that I could compile the macro statement in a data step. Unfortunately though, I could only run the macro from the variable when the entire macro code (from the %MACRO to the %MEND statement) was saved in the variable. See the code below.
%MACRO test(name);
data test;
*COMPILE MACRO STATEMENT;
pct=%nrstr('%');
name="new_&name";
beginning=pct||'MACRO '||strip(name)||'();';
*CODE TO BE INSIDE MACRO;
/*Note: SAS will encounter errors if you try to assign text containing macro
functions (e.g., %PUT, %IF, etc.) to a variable. To get around this, you must
put hide the % in the following syntax, %nrstr('%'), and concatenate/join the
syntax with the rest of the string */
code=pct||'PUT HELLO!;';
*COMPILE MEND STATEMENT;
end=pct||'MEND;';
call symput('MacroStatement',beginning||code||end); *Output var containing macro;
call symput('Execute',pct||strip(name)||';'); *Output var containing statement to run macro;
output;
run;
&MacroStatement
&Execute
%MEND;
%test(name1);
options mprint mlogic symbolgen nospool;
%let definition=abc;
%let mdef=macro &definition.;
%&mdef.;
%put TEST;
%mend;
%abc;

"For in" loop equivalent in SAS 9.3

I'm searching for a while an equivalent of the for in loop (like in Python or in R) in SAS 9.3 macro language. The DO loop seem's to be the solution but did't work exactly as I want.
I founded a way to do it in a data step with a DO loop but it don't work with the macro language.
For example, in a data step, this code is working :
DATA _NULL_;
DO i = 1,3,5,9;
PUT i;
END;
RUN;
And then the log prompt as expected :
1
3
5
9
When I try to do the same with an %DO loop in a Macro, I have an error.
%MACRO test();
%DO i = 1,2,4,9 ;
%PUT i = &i;
%END;
%MEND;
%test();
The log promp these messages :
ERROR: Expected %TO not found in %DO statement.
ERROR: A dummy macro will be compiled
I'm quite new in SAS and stackoverflow so I hope my question is no too stupid. It's so simple to do this in Python and R then it must have a simple way to do it in SAS.
Thank's for help - J. Muller
The closest I've ever come across to this pattern in SAS macro language is this:
%MACRO test();
%let j=1;
%let vals=1 2 4 9;
%do %while(%scan(&vals,&j) ne );
%let i=%scan(&vals, &j);
%put &i;
%let j=%eval(&j+1);
%end;
%MEND;
%test();
(Warning: untested, as I no longer have a SAS installation I can test this out on.)
You can certainly get around it this way:
options mindelimiter=,;
options minoperator;
%MACRO test();
%DO i = 1 %to 9 ;
%if &i in (1,2,4,9) %then %do;
%PUT i = &i;
%END;
%end;
%MEND;
%test();
However, I think you can usually avoid this sort of call by executing your macro multiple times rather than attempting to control the loop inside the macro. For example, imagine a dataset and a macro:
data have;
input x;
datalines;
1
2
4
9
;;;;
run;
%macro test(x);
%put &x;
%mend test;
Now you want to call %test() once for each value in that list. Okay, easy to do.
proc sql;
select cats('%test(',x,')') into :testcall separated by ' ' from have;
quit;
&testcall;
That works just as well as your %do in loop, except it's data driven, meaning if you want to change the calls you just change the dataset (or if your data changes, the call automatically changes!). In general, SAS is more effective when designed as data driven programming rather than as entirely written code.