I've built a simple macro which checks for the existence of a file (any kind), and then populates a global macro variable with a 0 or 1 to indicate the result. It keeps testing for the file's existence at a user-specified interval until a user-specified timeout. Using SAS 9.4 running interactively on Windows.
Being that I'm relatively inexperienced writing macro code, I thought I'd run it by you folks for suggestions to make this more robust.
By the way, I intend to use this following execution of a Python script called using the "X" command. The Python script sets a trigger file if the process completed successfully, and SAS will be looking for that trigger file. If that seems like a shortsighted approach, please let me know!
Here's the macro code, and a call to check for a file every half second for 10 seconds.
%MACRO CHECK_FILE_EXISTANCE (FILENAMEANDPATH= , /* FILE TO CHECK */
INTERVAL_TIME= , /* HOW LONG TO PAUSE BETWEEN CHECKS, SECONDS */
TIMEOUT= , /* TOTAL TIME BEFORE GIVING UP, SECONDS */
RESULT_INDC_VAR= ); /* NAME OF MACRO VARIABLE TO POPULATE WITH BOOLEAN RESULT */
/* DEFINE GLOBAL VAR TO HOLD OUTCOME */
%GLOBAL &RESULT_INDC_VAR;
/* DEFINE LOCAL MACRO VARS */
%LOCAL MAX_INTERVALS;
%LOCAL I;
/* DETERMINE NUMBER OF ITERATIONS REQUIRED */
%LET MAX_INTERVALS = %SYSEVALF(&TIMEOUT/&INTERVAL_TIME,CEIL);
/* BEGIN LOOPING */
%DO I = 1 %TO &MAX_INTERVALS;
%IF %SYSFUNC(FILEEXIST(&FILENAMEANDPATH)) %THEN %DO;
%LET &RESULT_INDC_VAR = 1; /* FILE EXISTS */
%GOTO EXIT;
%END;
%ELSE %DO;
%LET &RESULT_INDC_VAR = 0; /* FILE WAS NOT FOUND (YET) */
/* PAUSE BEFORE TRYING AGAIN */
DATA _NULL_;
X = SLEEP(&INTERVAL_TIME, 1);
RUN;
%END;
%END; /* END OF ITERATION */
%EXIT:
%IF &RESULT_INDC_VAR = 1 %THEN %DO;
%PUT NOTE: FILE EXISTS (&FILENAMEANDPATH);
%END;
%IF &RESULT_INDC_VAR = 0 %THEN %DO;
%PUT WARNING: FILE DOES NOT EXIST (&FILENAMEANDPATH);
%END;
%MEND;
%CHECK_FILE_EXISTANCE (FILENAMEANDPATH=C:\TESTFILE.TXT ,
INTERVAL_TIME= .5,
TIMEOUT= 10,
RESULT_INDC_VAR = FILE1_CHECK);
%PUT &FILE1_CHECK;
I am not a great fan of using global macro variables to return values. In this case it is not necessary; by using %sysfunc(sleep()) you can avoid the data step, and change the code to a function style macro:
%macro WaitForFile(FilenameAndPath , /* file to check */
interval_time=1, /* how long to pause between checks, seconds */
timeout=10 /* total time before giving up, seconds */
);
%local i rc;
/* begin looping */
%do i = 1 %to %sysevalf(&timeout/&interval_time,ceil);
%if %sysfunc(fileexist(&FilenameAndPath)) %then %do;
%put NOTE: File exists (&FilenameAndPath);
%*;1
%return;
%end;
/* pause before trying again */
%let rc=%sysfunc(sleep(&interval_time, 1));
%end; /* end of iteration */
%put WARNING: File does not exist (&FilenameAndpath);
0
%mend;
So, you can use the macro in code like
%if %WaitForFile(x:\test.txt) %then...
or
%let FileOK=%WaitForFile(x:\test.txt);
I changed the name of the macro to WaitForFile, as that seems to explain the function better. I dropped the all-caps style as that makes the code easier to read (I once heard that there is a scientific study to prove that!). And I simplified the code: No reason to put the number of intervals in a variable, it is only used once. Do not use %GOTO when you can use %RETURN. And I dropped the named parameter requirement (the "=") for the first parameter, as the meaning seems obvious when you see the macro used in code, and added defaults for the other two parameters.
One finale note: the macro comment "%*;" before the 1 is just a way of avoiding spurious blanks in the return value.
Hi,
I think it's fine of doing it this way, make sure to use triple ampersands to resolve the value of FILE1_CHECK
%IF &&&RESULT_INDC_VAR = 1 %THEN %DO;
%PUT NOTE: FILE EXISTS (&FILENAMEANDPATH);
%END;
%IF &&&RESULT_INDC_VAR = 0 %THEN %DO;
%PUT WARNING: FILE DOES NOT EXIST (&FILENAMEANDPATH);
%END;
Here is a little interesting paper explaining the situation:
https://www.lexjansen.com/phuse/2015/cc/CC08.pdf
- Cheers -
I am not a great fan of using global macro variables to return values. In this case it is not necessary; by using %sysfunc(sleep()) you can avoid the data step, and change the code to a function style macro:
%macro WaitForFile(FilenameAndPath , /* file to check */
interval_time=1, /* how long to pause between checks, seconds */
timeout=10 /* total time before giving up, seconds */
);
%local i rc;
/* begin looping */
%do i = 1 %to %sysevalf(&timeout/&interval_time,ceil);
%if %sysfunc(fileexist(&FilenameAndPath)) %then %do;
%put NOTE: File exists (&FilenameAndPath);
%*;1
%return;
%end;
/* pause before trying again */
%let rc=%sysfunc(sleep(&interval_time, 1));
%end; /* end of iteration */
%put WARNING: File does not exist (&FilenameAndpath);
0
%mend;
So, you can use the macro in code like
%if %WaitForFile(x:\test.txt) %then...
or
%let FileOK=%WaitForFile(x:\test.txt);
I changed the name of the macro to WaitForFile, as that seems to explain the function better. I dropped the all-caps style as that makes the code easier to read (I once heard that there is a scientific study to prove that!). And I simplified the code: No reason to put the number of intervals in a variable, it is only used once. Do not use %GOTO when you can use %RETURN. And I dropped the named parameter requirement (the "=") for the first parameter, as the meaning seems obvious when you see the macro used in code, and added defaults for the other two parameters.
One finale note: the macro comment "%*;" before the 1 is just a way of avoiding spurious blanks in the return value.
Thank you very much - your solution is much better than mine!
Hi, one little more thing, the right spelling is 'existence' with an -e
- Cheers -
It's finally time to hack! Remember to visit the SAS Hacker's Hub regularly for news and updates.
Learn how use the CAT functions in SAS to join values from multiple variables into a single value.
Find more tutorials on the SAS Users YouTube channel.
Ready to level-up your skills? Choose your own adventure.