I want to execute code which is independent of my current program via keyboard shortcuts within the Enhanced Editor in SAS 9.4 for Windows. I've achieved this with limited success being able to execute only macro statements. However, I want to be able to execute non-macro statements, too. How do I do this?
Here's what I've figured out so far.
General Setup
Get to the KEYS menu by either entering "KEYS" into the command prompt or submitting
dm 'keys';
For one of the keys, enter the definition
%put Hello, world!;
Save the new key binding by pressing Ctrl+s. For the purposes of this explanation, I will bind it to F7. Once saved, press F7 and "Hello, world!" will be printed to the log.
We can extend this concept further by placing the above code in a macro.
%macro HelloWorld();
%put Hello, world!;
%mend;
Compile the %HelloWorld macro. If we then bind %HelloWorld(); to F7, we can demonstrate that a macro may be called with a shortcut.
Via AUTOCALL
We can take things further yet and save our %HelloWorld macro as a program HelloWorld.sas. If we then put this in an AUTOCALL library (run %put %sysfunc(pathname(sasautos)); to find where those are located on your computer), we can execute it within any new SAS session.
It appears, however, that only macro statements work with this method. To demonstrate this, suppose that we instead defined %HelloWorld as
%macro HelloWorld();
data _null_;
put 'Hello, world!';
run;
%mend;
Again, save this as HelloWorld.sas and place it in an AUTOCALL directory. For me, when I try to execute this, I get the following error:
ERROR: The SAS/EIS product with which the procedure is associated is either not licensed for
your system or the product license has expired. Please contact your SAS installation
representative.
Via %INCLUDE
Since an AUTOCALL requires a macro to be compiled and called, I thought %INCLUDE might execute the code directly.
Create a file called HelloWorld.sas containing %put Hello, world!. Save it to a short file path. Then, in the KEYS menu bind F7 to %include "C:\Short Path\HelloWorld.sas";. Now F7 will print "Hello, world!" to the log.
If we instead save
data _null_;
put 'Hello, world!';
run;
to HelloWorld.sas and try to run it using our %INCLUDE shortcut, I receive the same error:
ERROR: The SAS/EIS product with which the procedure is associated is either not licensed for
your system or the product license has expired. Please contact your SAS installation
representative.
Misc. Attempts
I've also tried entering code directly into a KEYS definition, but again, it only seems to work for macro statements.
It might be possible to use %SYSFUNC, but my ultimate goal is to be able to use PROC SQL or data steps and I don't think %SYSFUNC can do this.
You can use the submit command, i.e. define a key as:
submit "data _null_ ; put 'Hello World!'; run;"
Also works with a macro call:
submit "%HelloWorld()"
Building off #Quentin's answer, if your datastep is huge you can write your datastep and save it as a compiled program as such:
/* store your datastep (below stored in WORK, can be stored in any defined library */
data male female / pgm=work.saved_program;
set sashelp.class;
if SEX="M" then output male;
else output female;
run;
Then as #Quentin suggested, go to your KEYS<DMKEYS> window and enter:
submit "data pgm = work.saved_program; describe; execute; run;"
This will submit your entire datastep saved in any library you choose.
Related
I've been using a macro library so that I can use macros without compiling them first. The problem is that when I change the macro and save it, then refresh my filename for the macro lib, this is not enough to update and use the new macro?
Anyone have any ideas why it is still using and compiling the old macro before it was saved?
The first time a macro is called, if it hasn't already been defined, SAS will check your autocall path and iterate through those locations trying to find it.
When it finds the macro in your autocall library it compiles it and saves the compiled version to your work folder. Subsequent calls to the macro will result in SAS using the compiled version of the macro.
In order for it to be refreshed (if you have made changes since it was compiled) you need to open the code to the macro and submit it again. That will redefine/recompile it for you.
Alternatively, you could also find the catalog in your work folder that contains the compiled versions of the macros and delete it from there (typically work.sasmacr).
Robert explains why you see the behavior.
I use the following to easily reinclude a changed macro. This assumes you have a FILENAME called MACROREF defined to the folder in question.
%include MACROREF(my_macro);
Obviously change the my_macro to the macro you need to be compiled.
filename macroref "c:\temp";
%include MACROREF(MacroOne);
If you have a folder full of macros (as stated in the comments) you can include the whole folder.
%include "%sysfunc(pathname(MACROREF))/*.sas";
This will recompile the whole folder. Just don't have any non-macro sas files in that folder, otherwise you are running them too.
I'm a relatively new SAS user, so please bear with me!
I have 63 folders that each contain a uniquely named xls file, all containing the same variables in the same order. I need to concatenate them into a single file. I would post some of the code I've tried but, trust me, it's all gone horribly awry and is totally useless. Below is the basic library structure in a libname statement, though:
`libname JC 'W:\JCs\JC Analyses 2016-2017\JC Data 2016-2017\2 - Received from JCs\&jcname.\2016_&jcname..xls`
(there are 63 unique &jcname values)
Any ideas?
Thanks in advance!!!
This is a common requirement, but it requires a fairly uncommon knowledge of multiple SAS functions to execute well.
I like to approach this problem with a two step solution:
Get a list of filenames
Process each filename in a loop
While you can process each filename as you read it, it's a lot easier to debug and maintain code that separates these steps.
Step 1: Read filenames
I think the best way to get a list of filenames is to use dread() to read
directory entries into a dataset as follows:
filename myfiles 'c:\myfolder';
data filenames (keep=filename);
dir = dopen('myfiles');
do file = 1 to dnum(dir);
filename = dread(dir,file);
output;
end;
rc = dclose(dir);
run;
After this step you can verify that the correct filenames have been read be printing the dataset. You could also modify the code to only output certain types of files. I leave this as an exercise for the reader.
Step 2: use the files
Given a list of names in a dataset, I prefer to use call execute() inside a data step to process each file.
data _null_;
set filenames;
call execute('%import('||filename||')');
run;
I haven't included a macro to read in the Excel files and concatenate the dataset (partly because I don't have a suitable list of Excel files to test, but also because it's a situational problem). The stub macro below just outputs the filenames to the log, to verify that it's running:
%macro import(filename);
/* This is a dummy macro. Here is where you would do something with the file */
%put &filename;
%mend;
Notes:
Arguably there are many are many examples of how to do this in multiple places on the web, e.g.:
this SAS knowledge base article (http://support.sas.com/kb/41/880.html)
or this paper from SUGI,
However, most of them rely on the use of pipe to run a dir or ls command, which I feel is the wrong approach because it's platform dependent and in many modern environments the ability to pipe shell commands will be disabled.
I based this on an answer by Daniel Santos in communities.sas.com, but, given the superior functionality of stackoverflow I'd much rather see a good answer here.
I have a folder with various flat files. There will be new files added every month and I need to import this raw data using an automated job. I have managed everything except for the final little piece.
Here's my logic:
1) I Scan the folder and get all the file names that fit a certain description
2) I store all these file names and Routes in a Dataset
3) A macro has been created to check whether the file has been imported already. If it has, nothing will happen. If it has not yet been imported, it will be imported.
The final part that I need to get right, is I need to loop through all the records in the dataset created in step 2 and execute the macro from step 3 against all file names.
What is the best way to do this?
Look into call execute for executing a macro from a data step.
The method I most often use, is to write the macro statements to a file and use %include to submit it. I guess call execute as Reeza suggested is better, but I feel more in control when I do it like this:
filename s temp;
data _null_;
set table;
file s;
put '%macrocall(' variable ');';
run;
%inc s;
I have a macro written in powerpoint and i need to call it from my perl script, it is possible to call a macro from Excel using $Excel->Run("MYMACRONAMEHERE"); but using "Run" with powerpoint is giving the below error:
OLE exception from "Microsoft PowerPoint 2010":
Application.Run : Invalid request. Sub or function not defined.
Win32::OLE(0.1709) error 0x80048240 in METHOD/PROPERTYGET "Run"
below is the perl i am usign to call the macro from powerpoint:
my $filename = "<path>";
my $PptApp = Win32::OLE->GetActiveObject('PowerPoint.Application')|| Win32::OLE->new('PowerPoint.Application', 'Quit');
$PptApp->{Visible} = 1;
my $Presentation = $PptApp->Presentations->Open({FileName=>"$filename",ReadOnly=>0});
$PptApp->Run("macro_name");
You're using the power point Application object to run an Excel macro. That's not going to work. You'll need to get an instance of Excel and use its Application object to run the Excel macro. Alternatively, you could copy the VBA code into a PowerPoint module.
The macro must be defined and, I'm pretty sure, must be declared as Public.
And, from MSDN (http://msdn.microsoft.com/en-us/library/office/ff744221(v=office.15).aspx):
The name of the procedure to be run. The string can contain the following: a loaded presentation or add-in file name followed by an exclamation point (!), a valid module name followed by a period (.), and the procedure name. For example, the following is a valid MacroName value: "MyPres.ppt!Module1.Test."
However, if the procedure is declared Public and is part of a loaded add-in, you should only need to supply the procedure name.
I have a .txt file that I need to attach in a column of my sheet, and i have the path to this file.
So I need to read this path and attach the file in another column programmatically. Is there a way to do it?
thanks in advance.
Indeed there is! And using by using macros it is quite easy to do.
Enabling macros
Go to the Tools > Options menu and click on the Security section under OpenOffice.org. Once there, click the Macro Security button. Now on the Security Level Tab, make sure that your settings will allow you to run Macros.
My settings are on low because I'm the author of all the macros I run, if you are not sure that this will be your case you might want to use a higher setting.
Note: Be careful, if you are unlucky or live in the 90's an evil macro can cause serious damage!
Creating a new macro
Now that you can run them, you must create a new macro. OpenOffice accepts a wide range of languages including Python, but since you didn't specified any I'll use OO's version of basic here.
Go to Tools > Macros > Organize Macros > OpenOffice.org Basic, and once there add a new module under your file's tree. Give it a meaningful name.
The actual macro
Once you create a new module the editor screen will pop up, write this code below:
Sub DataFromFile
Dim FileNo As Integer
Dim CurrentLine As String
Dim File As String
Dim Msg as String
Dim I as Integer
' Get the filename from the cell, in this case B1.
currentSheet=ThisComponent.CurrentController.ActiveSheet
fileName = currentSheet.getCellRangeByName("B1").getString
' Create a new file handler and open it for reading
FileNo = FreeFile
Open fileName For Input As #FileNo
I = 0
' Read file until EOF is reached
Do While not eof(FileNo)
' Read line
Line Input #FileNo, CurrentLine
' Define the range to put the data in as A4:A999 '
curentCell = currentSheet.getCellRangeByName("A4:A999").getCellByPosition(0,I)
' Select the I-th cell on the defined range and put a line of the file there
curentCell.String = CurrentLine
'Increase I by one
I = I + 1
Loop
Close #FileNo
End Sub
To test it, just create a text file and put something in it, then put the path to it on cell B1 and run the macro. You can run the macro in many ways, for test purposes just use the Run button on the same window that you used to create the module. This is the expected result:
Note: If you are unfamiliar with linux, don't be intimidated by that file path, it's just how they are on linux. This would just work the same with windows and it's file path structure.
Further improving the macro
I wrote the code above with the goal of making it as easy to understand as possible, therefore the macro have plenty of room for improvement, such as:
Being able to show the data retrieved on multiple columns/A single column/Something else
Once you have retrieved the data from the file, you can display it on your spreadsheet in nearly anyway you want it. Let me know if the way you initially intended was not addressed and I will edit the answer.
Having to re-run the macro every time you want the data updated.
This is easily fixed. There are many ways to automatize the macro execution, the one I'm most familiar with consists on making it run on a loop in conjunction with a delay of, say, 5 seconds and making it start as soon as the file loads.
Sub Main
Do While True
DataFromFile()
Wait(5000)
Loop
End Sub
And from now on you should call the Main sub instead of the DataFromFile.
To make the macro run at start-up go to Tools > Customize on the Events tab and select Open Document from the list then click on the Macro button. On the dialog to select the macro, pick Main. Now close the document, reopen it, and voila!
Using Cell Ranges
It's easier to keep your code and make changes to it if you name the cell ranges and use their names instead of their absolute address. To name a range (or a single cell) you must first select it then click on Data > Define Range to give it a name, for example B1 could be called 'FilePath' and A4:A999 could be called 'DataRange'. This way if you ever need to change them, you don't have to change the macro, just the defined range name.
Don't forget to update the code to look for the range instead of the address, for example, this bit of code:
getCellRangeByName("A4:A999")
would be rewritten to
getCellRangeByName("DataRange")
Error checking
It is a good idea to check and deal with error or unexpected events. What if the file doesn't exists? What if it is bigger than the defined range?
Further reading
Official reference regarding files for OpenOffice Basic macros.
A guide on different ways to run a macro
A great introduction to macro programming