Resolving macro containing %eval in macro variable name, SAS - variables

this is a toy example in order to help with a larger problem I have. It essentially involves using a %eval() macro when referencing a bigger macro variable name.
I have created a macro variable x_2, which uses the values of the loop, '&it', the variable is created successfully as can be seen by the final output, however I can only put it to the log without evaluating &it+1, which I will need to do when using a loop bigger than size 1.
It seems to resolve x_ first, giving a warning, before then evaluating x_2 as a whole and giving the output.
I realise this is just a problem about how to reference macros correctly, but I cannot find any examples where it uses an evaluation as part of a macro variable name.
Thanks.
%macro testing;
%DO it = 1 %TO 1;
data dataset;
s=100;
t=99;
run;
data _null_;
set dataset;
if s = 100 then do;
call symput("x_%eval(&it+1)",t);
end;
run;
%put "&x_%eval(&it+1)";
%put &x_2;
%END;
%mend testing;
%testing;
LOG OUTPUT
MLOGIC(TESTING): %PUT "&x_%eval(&it+1)"
WARNING: Apparent symbolic reference X_ not resolved.
SYMBOLGEN: Macro variable IT resolves to 1
SYMBOLGEN: Macro variable X_2 resolves to 99
" 99"
MLOGIC(TESTING): %PUT &x_2
SYMBOLGEN: Macro variable X_2 resolves to 99
99
MLOGIC(TESTING): %DO loop index variable IT is now 2; loop will not iterate again.
MLOGIC(TESTING): Ending execution.

Indeed, SAS does the variable substitution before calling the %eval function
The easiest solution is to call %eval in an earlier statement
%let xNr = %eval(&it+1);
%put "&&x_&xNr";
The double ampersand serves to delay the evaluation of x_&xNruntil &xNr is evaluated to 2 and not have the warning that x_ is undefined.
SYMBOLGEN: Macro variable IT resolves to 1
SYMBOLGEN: && resolves to &.
SYMBOLGEN: Macro variable XNR resolves to 2
SYMBOLGEN: Macro variable X_2 resolves to 99
" 99"

Agree with Dirk's answer. But as a thought exercise, it is possible to achieve the desired result with ugly use of quoting functions:
%let x_2=99;
%let it=1;
%put %unquote(%nrstr(&x_)%eval(&it+1));
So %nrstr hides the & until after the %eval has done its work (I think. :)
Update:
#Shenglin added a similar answer in a comment that I like better than my approach above:
%put %superq(x_%eval(&it+1));
This works because %superq() is unusual in taking a macro variable name as a parameter (no leading &), not a macro variable reference. So you can use %EVAL to generate part of the name of the macro variable. You could %unquote() if wanted, but it should not be necesssary.

It is much easier to build the macro variable name into another variable and then expand the value.
%let t2=found;
%let mvar=t%eval(1+1);
%put &&&mvar;

Could you try "&&x_%eval(&it+1)"?

Related

How to set macro condition for day of today in any of list

I wanna set the code run at days: 5,11,16,22,28 monthly but it doesn't work.
%if %sysfunc(day(%sysfunc(today()))) in (5,11,16,22,28) %then %do
How can I fix it? Thanks much.
The IN operator works in a SAS 9.2 Macro but you need to define system options. The MINOPERATOR (Macro-In-Operator) must be turned on and in your case, the MINDELILITER too must be specified as you are using comma delimited values.
More information can be found in Getting the IN operator to FUNCTION inside a SASĀ® Macro
options minoperator mindelimiter=',';
%if %sysfunc(day(%sysfunc(today())-1)) in (5,11,16,22,28) %then %do;
%put do smth...;
%end;
do smth...
Notice the -1 in the above example to replicate March 22.

How to run VB.NET with X command in SAS through Enterprise

I am trying to use the following sas programs to convert multiple rtf files into pdf files by using the VB.NET (X command). It worked perfectly in PC sas. However, while using it in the SAS Enterprise, it gave no error message but didn't perform the pdf convertion.
Does anyone know what's happening in here? What should I do to make this work again? Thanks!
%macro vbs_pdf(rtfname=,pdfname=);
data _null_;
length vbscmd $ 400;
file "temp.vbs";
put 'Dim ObjWord';
put 'set objWord = CreateObject("Word.Application")';
put 'objWord.Visible = True';
vbscmd='objWord.Documents.Open("'|| "&rtfname" ||'")';
put vbscmd;
vbscmd='objWord.ActiveDocument.SaveAs "'||"&pdfname"||'", 17';
put vbscmd;
put 'objWord.ActiveDocument.Close(False)';
put 'objWord.Application.Quit(False)';
run;options noxwait;
data _null_;
command="START /WAIT CScript temp.vbs //NoLogo";
call system(command);
command2="DEL temp.vbs ";
call system(command2);
run;
%mend;
options noxwait;
data &prefix._toc;
set &prefix._toc;
call symput('count',compress(put(_n_,best.)));
run;
proc sql noprint;
select filename
into : rtf1- :rtf&count
from &prefix._toc;
run;
%macro cvtpdfs;
%do i=1 %to &count;
%let mypdf=%scan(&&rtf&i,1,'.');
%vbs_pdf(rtfname=&file2path./&&rtf&i,pdfname=&curpath/documents/pdf/&mypdf..pdf);
%end;
%mend;
%cvtpdfs;
By default SAS EG has X commands disabled. In your SAS configuration files for EG you will see -NOXCMD. You will see more here: https://blogs.sas.com/content/sasdummy/2009/11/19/using-the-x-and-systask-commands-from-sas-enterprise-guide/

Automate SAS program for different sites using %include

The problem I have is, I use the same program for reports on multiple sites. I have to run the code for each of them individually. I'm looking to automate my process to call the code and run for all sites in a go. This is the code I tried using but it is not working.
data _null_;
array sites {2} _temporary_ (SiteA SiteB);
do k = 1 to dim(sites);
%let site = sites(k);
%include '...path\SitesWait.sas';
end; run;
The code in SiteWait works perfectly on its own. I defined a macro variable called site, which sets the site for the code to run.
The error I keep getting is 'ERROR 117-185: There was 1 unclosed DO block.'
Thanks
You cannot include another multi-step program into the middle of a DATA step. %include just puts lines from the file into the program stream as if the lines had been typed into program. When SAS sees the first DATA or PROC statement in the included file it will stop compiling the DATA step in your main program and run it. That is why your DO loop is not seeing the END statement.
You could just use a data step to generate code that sets the parameter and includes the program.
data _null_;
length site $20 ;
do site='SiteA','SiteB' ;
call execute(cats('%nrstr(%let) site=',site,';'));
call execute("%include '...path\SitesWait.sas';");
end;
run;
What you have created is basically a macro with a single parameter named SITE. In fact you could possible just use %INCLUDE as the body of the macro definition. Then you could just code the calls you want instead of trying to process some list of values.
%macro siteswait(site);
%include '...path\SitesWait.sas';
%mend;
%siteswait(SiteA)
%siteswait(SiteB)
So you wish to include different files based on prior dataset. I suggest you create the file instead of reading it from array, but keeping with the spirit of your initial request:
%let basePath= c:\foo;
data _NULL_;
array sites {2} $5. ('SiteA' , 'SiteB');
do k = 1 to dim(sites);
site = sites(k);
call execute('%nrstr(%put &basePath.\'||site||';)');
end;
run;
This produces
c:\foo\SiteA
c:\foo\SiteB
You can easily replace the %put command with %include to accomplish what you wish.
Edit: What I suggest you try to do instead is something like this:
data includes;
format path $30. file $10.;
input path $ file $;
cards;
c:\foo\ file1.sas
c:\foo\ file2.sas
c:\Bar\ file1.sas
c:\Bar\ file2.sas
; run;
data _null_;
set includes;
call execute('%nrstr(%put '||strip(path)||''||strip(file)||';)');
run;
You should do like this :
main.sas
%macro ProcessList;
%let list_of_site=SiteA|SiteB|SiteC;
%let k=1;
%do %while (%qscan(&list_of_site, &k,|) ne );
%let site = %scan(&list_of_site, &k,|);
%include 'H:\desktop\SAS\test_inc.sas';
/*%put site=&site;*/
%let k = %eval(&k + 1);
%end;
%mend ProcessList;
%processList;
test_inc.sas
%put site=&site;
Result:
site=SiteA
site=SiteB
site=SiteC
With a macro fonction it is simple. You can replace %include with %put.
You can get the list of site from a dataset with this technic :
data test;
infile datalines dsd;
input site : $200. ;
datalines;
SiteA,
SiteB,
SiteC,
SiteD,
SiteE,
SiteF,
;
run;
proc sql noprint;
select quote(trim(site), "'") into : list_of_site separated by "|" from work.test;
quit;
Regards
If the sites don't change often, and if there are not too many of them, this may be the simplest approach:
%let site=siteA;
%include '...path\SitesWait.sas';
%let site=siteB;
%include '...path\SitesWait.sas';
%let site=siteC;
%include '...path\SitesWait.sas';
Be careful though. You may want to include code at the top of SitesWait.sas that 'resets' you SAS session. ie. clears any macro values you plan on using, deletes any datasets in your work folder etc. Otherwise the second call to SitesWait.sas may be affected by the prior run.

using macro variable to read folder content in SAS 9.4 [duplicate]

This question already has answers here:
Why won't my macro variable resolve?
(2 answers)
Closed 5 years ago.
I'm trying to get the list of files in a directory with a SAS macro that uses a macro variable to specify dynamically the folder name. The code I run is the following:
%macro veicolo(codice_veicolo);
filename pipedir pipe ' dir "some_path\&codice_veicolo" /S' lrecl=5000;
data &codice_veicolo;
infile pipedir truncover;
input line $char1000.;
length directory $1000;
retain directory;
if line =' ' or
index(upcase(line),'<DIR>') or
left(upcase(line))=:'VOLUME' then
delete;
if left(upcase(line))=:'DIRECTORY OF' then
directory=left(substr(line,index(upcase(line),'DIRECTORY OF')+12));
if left(upcase(line))=:'DIRECTORY OF' then
delete;
if input(substr(line,1,10),?? mmddyy10.) = . then
substr(line,1,10)='12/31/2999';
date=input(substr(line,1,10),?? mmddyy10.);
format date mmddyy10.;
run;
proc sort data=&codice_veicolo;
by directory descending date;
run;
data folder_&codice_veicolo(drop=i line);
set &codice_veicolo;
by directory;
length filename $75;
retain number_of_files_in_directory directory_size;
if first.directory then
do;
number_of_files_in_directory=input(scan(line,2,' '),32.);
call symput(nfiles,number_of_files_in_directory);
directory_size=input(scan(line,4,' '),comma32.);
end;
file_size=input(scan(line,3,' '),comma32.);
filename=' ';
do i=4 to 100;
filename=trim(left(filename))||' '||scan(line,i,' ');
if scan(line,i,' ')=' ' then
leave;
end;
if index(upcase(line),'FILE(S)') then
delete;
if date ge '30DEC2999'd then
delete;
run;
%mend;
When I then execute the macro with the argument codice_veicolo being the name of the folder I want to search in, I get the following error:
Output Err std:
The system cannot find the path specified.
NOTE: 20 records were read from the infile PIPEDIR.
The minimum record length was 0.
The maximum record length was 90.
NOTE: The data set WORK.JL6AME1A6FK000442 has 2 observations and 3 variables.
NOTE: DATA statement used (Total process time):
real time 0.05 seconds
cpu time 0.01 seconds
I supposed that for some reason it could not resolve the macro variable, but if I run:
%let pgmpath = %sysfunc(pathname(pipedir));
%put &pgmpath;
I get the proper path and the proper directory, therefore I assume the problem is in the infile statement. The code runs fine without using macro variables.
I am using SAS 9.4 on Windows 8. Any ideas??
Thank you in advance :)
Luca
Macro variable references are not expanded inside of single quotes.
Try this instead.
filename pipedir pipe %sysfunc(quote(dir /s "some_path\&codice_veicolo")) lrecl=5000;

SAS: How to run a loop on a set of input variables for a function

I'm basically trying to make a program that can extract the relevant variables from a CSV file and perform stationarity tests on these timeseries variables without having to type out the variable names manually. I'm fairly new and maybe there's redundancies, but I've basically embeded the variable names into variable1, variable2, etc...
So when I've tested the ADF macro and input the variable as:
proc arima data=&y;
identify var= &variable1 stationarity =(adf = 3);
run;
it works, but I'm struggling to automate this process and would appreciate any help you could offer. Thanks.
Here is what I have to far:
proc import datafile=".....csv"
out=data
dbms=csv
replace;
getnames=yes;
run;
proc contents data=data out=contents noprint;
run;
data contents;
set contents(keep = name);
if Name = "Quarter" then delete;
run;
data _NULL_;
set contents;
call symputx(cats('variable',strip(_n_)),name);
run;
data want;
do i=1 to 4;
expert=symget(cats('variable',i));
output;
end;
run;
%macro ADF(y = ,x = );
proc arima data=&y;
identify var= &x stationarity =(adf = 3);
run;
%mend ADF;
You're very close... You just need to look into call execute. It will allow you to build commands and have them execute after the datastep finishes.
In the below code we're building the calls to the macro that we want to run. They will be exected as instructions after the datastep has finished processing. This will allow us to use values within the datastep to build the commands:
data want;
do i=1 to 4;
expert=symget(cats('variable',i));
call execute (cats('%ADF(y=data,x=',expert,');'));
end;
run;
There are other ways to do it but this is the simplest based on what you already have in place.
Just make sure your macro is defined prior to running the datastep.