SAS: Using the Put statement to create dynamic code - dynamic

I'd like to create dynamic code using the PUT statement. According to this document from SUGI 29 (http://www2.sas.com/proceedings/sugi29/175-29.pdf),
put
"data XXXXX; "
/ 'infile "&datadir/&compid&filetype" missover ls=' tbla_fle
';' / 'input'
;
is equivalent to running
data onecomp ;
infile
"&datadir/&compid&filetype"
missover ls = 268 ;
input
However, when I try something similar to their example, the code enclosed in the PUT statement doesn't run and is instead written to the SAS Output Log:
data _NULL_;
put // "data put_test;" / "b=2;" / "run;";
run;
In Output Log:
data put_test;
b=2;
run;
I've checked the SAS documentation, and it seems that PUT is only used to "Write lines to the SAS log, to the SAS output window, or to an external location that is specified in the most recent FILE statement." Nowhere does it say that it can be used to create dynamically generated code.
I know that I must be missing something, but I'm not sure what. I'm using SAS Enterprise Guide 4.1.
Thank you!

The idea is to use put to write your generated code to a file. You then %include the file into your SAS session to run it. What you're missing is a file statement and the %include directive.
data _null_;
file 'temp.sas'; /* redirects put to a file instead of the SAS log */
put
"data XXXXX; "
/ 'infile "&datadir/&compid&filetype" missover ls=' tbla_fle
';' / 'input'
;
run;
%include 'temp.sas';

Related

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;

Attaching file for a SAS program

I am new to SaS and i tried performing a simple query on sas enterprise guide.
The program is as follows:
libname ISS meta library="SQL - ISS" metaout=data;
Proc Sql;
select *
from MARKET_OPTION_DAY
where contract_market_code = '023A61'
and REPORT_DATE between '1/1/13' and '6/30/15';
QUIT;
the error im getting is the following : FILE work.MARKET_OPTION_DAY.DATA does not exist.
I dont understand whats wrong because i can view the file in the "SQL-ISS" library
If a file is located in a library, then you need to prefix with the library name.
proc sql;
select * from ISS.market_option_day
/* other stuff here ...*/
;
quit;
For example.

SAS - Reading a File Backwards?

I need SAS to read many large log files, which are set up to have the most recent activities at the bottom. All I need is the most recent time a particular activity occurred, and I was wondering if it's possible for SAS to skip parsing the (long) beginning parts of the file.
I looked online and found how to read a dataset backwards, but that would require SAS to first parse everything in the .log file into the dataset first. Is it possible to directly read the file starting from the very end so that I can stop the data step as soon as I find the most recent activity of a particular type?
I read up on infile as well, and the firstobs option, but I have no idea how long these log files are until they are parsed, right? Sounds like a catch-22 to me. So is what I'm describing doable?
I'd probably set up a filename pipe statement to use an operating system command like tail -r or tac to present the file in reverse order to SAS. That way SAS can read the file normally and you don't have to worry about how long the file is.
If you mean parsing a sas log file, I am not sure if reading the log file backward is worth the trouble in practice. For instance, the following code executes less than a tenth of a second on my PC and it is writing and reading a 10,000 line log file. How big is your log files and how many are there? Also as shown below, you don't have to "parse" everything on every line. You can selectively read some parts of the line and if it is not what you are looking for, then you can just go to the next line.
%let pwd = %sysfunc(pathname(WORK));
%put pwd=&pwd;
x cd &pwd;
/* test file. more than 10,000 line log file */
data _null_;
file "test.log";
do i = 1 to 1e4;
r = ranuni(0);
put r binary64.;
if r < 0.001 then put "NOTE: not me!";
end;
put "NOTE: find me!";
do until (r<0.1);
r = ranuni(0);
put r binary64.;
end;
stop;
run;
/* find the last line that starts with
NOTE: and get the rest of the line. */
data _null_;
length msg $80;
retain msg;
infile "test.log" lrecl=80 eof=eof truncover;
input head $char5. #;
if head = "NOTE:" then input #6 msg $char80.;
else input;
return;
eof:
put "last note was on line: " _n_ ;
put "and msg was: " msg $80.;
run;
/* on log
last note was on line: 10013
and msg was: find me!
*/