SAS functions Vim menu Vim Functions for SAS Programming


Introduction

I wrote these three functions to help me program SAS with the Vim text editor in GUI mode. The first allows me to run SAS in batch mode on a program I'm currently editing in Vim (I'm using Vim 8.1 currently because that's what I have to use for my job). In addition, there are functions to load the .log and .lst files for a SAS program being edited, and to run a log checking program on the current buffer, allowing the use of Vim's quickfix functionality to check SAS log files.

Note that I use these functions many times a day and they've been working fine for me on both Windows and Unix.

The code to add the three functions to the Vim Tools menu is given as well as code to make the assignments to function keys. All three Vim functions for using SAS, the menu and function key code, a SAS log check script, and an example wrapper script are available for download in this zip file. Current version is 1.1, updated 21Sep2009 to work better with Windows and still work on Unix.

Regarding the use of these functions with Windows and PC SAS: two suggestions I received (thank you Laurie Samuels) that appear to work fine are removing the fnameescape() calls from the LoadSASLogLst() function (it turns out these calls were overkill and are not needed for Unix use, either) and updating the RunSAS() function to work with PC SAS. The latter modification is a comment in the function that can be uncommented. Note that these functions may take a little bit of adjustment (well, the ChkSASLog function, especially) depending on your local environment.


Usage Notes

These functions were developed for use with SAS in a Unix environment so they reference Unix style paths and commands. But they could be adapted to Vim and SAS in a Windows environment quite easily (still using batch mode SAS). File extensions are used for determining the file types and follow SAS' defaults (.sas for program, .log for log file, and .lst for list output). These can be changed if you use other extensions for some reason.

I keep the code for these functions in my $HOME/.gvimrc file. I haven't tried these functions in a console Vim environment, though they may work. The functions use Vim tabs when loading files for editing, thus I use them in GUI Vim (console Vim can do tabbed editing, as well).

One option for Vim that may help you use these functions is to set the autoread option (set autoread in .gvimrc). Thus you should get your already-loaded .log and .lst files automatically updated when they change, such as when you rerun your .sas file. This may take a few seconds depending on how busy your Unix server (or Windows computer) is.

Below you'll find more detailed descriptions of each of the functions and the actual code used.

Run SAS on Current File/Buffer

SAS runs against a program file on disk, so this function, RunSASonCurrentFile(), will try to save the file before the run if the file has been modified in the editor.

This function will automatically load the .log and lst files that get created from the SAS run (or possibly just the log file if you have no list output) into separate tabs, if they are not already open in the editor (note: RunSASonCurrentFile() calls LoadSASLogList()).

The function gives its messages at the bottom of the Vim screen where other Vim messages appear. Any messages from SAS on the command line should appear here, if any, as well.

If the .log and .lst files are already open in the editor, Vim will (hopefully) prompt to load the changed files after SAS runs as it normally does when files being edited are changed on disk by another process (such as another SAS run)--if you have not set autoread in .gvimrc. This is Vim behavior, not a behavior of the function. In some cases, you may need to force your window manager to refocus on the Vim window for this to happen (e.g. by opening a pull down menu).

Be sure to set the location of SAS on your machine in the system() call below.


  " Run SAS on the current file/buffer. Assumes you have a SAS program in
  " the current buffer. File will be saved before running SAS, if it has 
  " been modified. SAS will run on the saved file on disk. Two tabs will
  " be opened, one for the log and one for the list, assumed to be in the
  " same location and with the same basename as the SAS program file.
  function! RunSASonCurrentFile()
    " Make sure this is a SAS program file (ends with .sas) so that
    " we don't run SAS on a log file or similar.
    :let checkSASpgm=match(expand("%"),"\.sas")

    " If we did not match .sas in the file name, end this function with
    " a warning msg
    if checkSASpgm==-1
       :echo "*** Current file is not a SAS program.  SAS run has been canceled."
       :return
    endif

    " Ask user if we want to run SAS so we don't accidentally run it.
    :let l:answer = input("Run SAS? Y/N ") 
    :if (l:answer == "Y" || l:answer == "y")

    " If file has been modified, save it before running
    if exists(&modified)
       :echo "*** Saving modified file before SAS run..."
       :w
    endif

    " Run SAS on path/file name (modify to your location of sas)
    :echo "*** Running SAS..."
    let returntxt = system("/usr/local/bin/sas -nodms " . shellescape(expand("%:p")))
    " The following may work for your Windows system. Comment the line above and uncomment 
    " the two lines below and make them one long line.
    "let returntxt = system("\"" . shellescape("C:\\Program\ Files\\SAS\\SAS\ 9.1\\sas.exe") 
    ".  "\ -nosplash" . "\ -sysin" . "\ " . shellescape(expand("%:p")) .  "\"") 

    " Shows the return messages from the SAS commandline (may be useful
    " if no log produced)
    :echo "*** SAS commandline: " . returntxt

    :call LoadSASLogList()

    :else
    :echo "SAS Run cancelled."

    " endif for the Run SAS? check
    :endif
  endfunction

Load SAS Log and Lst Files

The LoadSASLogLst function loads the SAS .log and .lst files for a SAS program being edited in the current buffer. It will load these files into separate tabs in Vim. This is handy if you want to see those files for a SAS program file you are examining but don't want to run in SAS. It uses the basename of file being edited to find the associated .log and .lst files.


  function! LoadSASLogLst()
    " Assumes you are editing a SAS program.   The log and lst file names will
    " be based on the SAS program basename (the file in the current buffer) and
    " are assumed to end in .log and .lst, as produced by SAS by default.

    " Load the SAS log file in a tab, if it is not already loaded & it exists
    :let log=bufexists(expand("%:p:r") . ".log")
    :if log==0
      :if filereadable(expand("%:p:r") . ".log")
        :execute "tabedit " . expand("%:p:r") . ".log"
      :else
        :echo "*** SAS log file does not exist."
      :endif
    :endif

    " Load the SAS lst file in a tab, if it is not already loaded & it exists
    :let lst=bufexists(expand("%:p:r") . ".lst")
    :if lst==0
      :if filereadable(expand("%:p:r") . ".lst")
        :execute "tabedit " . expand("%:p:r") . ".lst"
      :else
        :echo "*** SAS lst file does not exist."
      :endif
    :endif
  endfunction

Checking the SAS Log File

The CheckSASLog function helps in debugging a SAS program, allowing you to link a list of errors to the lines in the log where the errors occurred and jump to specific errors. This is also the function that requires the most modification to make work the way you want.

The Vim quickfix functionality (see :he quickfix in the Vim help system) makes jumping from error to error (or warning to warning) easy, so it is a good idea to take advantage of this. I've had it working in at least two different ways over the years: using Vim's internal grep capability (see :he grep), and using an external script or program that does the log checking and returns a list of errors, warnings, and other questionable messages from the SAS log.

In order to use Vim's quickfix mechanism fully, the list of errors and warnings needs to be linked to the SAS log file via the file name of the log. (Imagine if multiple SAS programs and logs were open in the editor, how would Vim know which list of errors goes with which log file? By the file name.)

Note: a SAS log may contain dozens of warnings and errors, but they may be caused by the first error and then cascaded into other errors from that point on. So, having a complete list of errors may not be indicative of the amount of debugging necessary. Always start with the first error found. Vim's quickfix feature will automatically jump to the first error found when the quickfix window opens.

The Unix grep program can provide a file name (GNU grep's -H option, for example) in the list of errors/warnings. Further below, I present a simple SAS Log checking shell script using the GNU grep program that works with Vim's quickfix mode. This demonstrates the use of an external tool. You can also use Vim's internal grep search facility to do something similar.

If you use another external log-checking program, make sure it returns the file name for each error, in addition to the line number and error text (and modify the grepformat in the CheckSASLog function appropriately). Otherwise, depending on your situation (e.g. a provided log check utility you can't change that does not provide a file name), you'll need to write a small wrapper script to call that check utility and do the addition of the file name to each error returned.


  " Check SAS log files for errors, warnings and other problems
  " Assumes you are editing the file to be checked when invoking this function.
  " No assumption about log file name extension is made so you can check any file.
   function! CheckSASLog()
    " Go to the first line of the file
    :0

    " Set grepformat for use with the program used by grepprg. NOTE:
    " quickfix needs a file name! Example grepformat when a file name is
    " returned by grepprg program
    set grepformat=%f:%l:%m

    " Example grepformat with no file name returned by the program used
    " by grepprg.
    "set grepformat=%l:%m

    " Save current grepprg setting
    let _grepprg=&grepprg

    " Define the program to use for searching the SAS log
    :set grepprg=~/bin/cklog

    " Run grepprg on the current file 
    grep %:p

    " Set grepprg back to its original setting
    let &grepprg=_grepprg

    " Open the quickfix error window 
    :cope
  endfunction

Using Vim's Internal "grep" to Search the SAS Log

A way to use Vim's internal grep function, which is more portable than external tools, is the following:

1. Define grepprg to be "internal":

:set grepprg=internal
2. Change the line "grep %:p" above to include the search terms you want:
grep /warning\|error\|uninit\| 0 obs\|no obser\|repeats of\|not found\|not valid\|invalid\|overwritten/c\/g %:p
The "\|" joining the terms above means "or". The "/\c" part near the end of the line means to use case-insensitive searching. The "g" near the end means to return all matches even repeats (this may not be what you want as it will find all matches on a line when usually one match is enough). The search terms can use Vim's regular expressions, so can be more complicated. Note the similarity in the search pattern to the external script example.

Menu Assignments and Key Mappings for the SAS Functions

You can relabel these, but note that you need to escape ('\') the spaces in the labels. Obviously, you can assign the functions to any function keys you aren't currently using, as well, and in an order that makes sense to you.


  " Add a separator before the SAS menu items
  menu Tools.-Sep-     :

  " Assign RunSASonCurrentFile function to the tools menu
  menu Tools.Run\ SAS\ on\ Current\ File  :call RunSASonCurrentFile()

  " Assign  LoadSASLogLst function to the tools menu
  menu Tools.Load\ SAS\ Log\/List\ for\ Current\ File :call LoadSASLogLst()

  " Assign  ChkSASLog function to the tools menu
  menu Tools.Check\ SAS\ Log :call CheckSASLog()

  " Map RunSASonCurrentFile to a function key
  map <F10> :call RunSASonCurrentFile()

  " Map LoadSASLogLst to a function key
  map <F11> :call LoadSASLogLst()

  " Map CheckSASLog to a function key
  map <F12> :call CheckSASLog()

A Simple SAS Log File Checking Script

This is the cklog program noted in the grepprg definition in the CheckSASLog() function. This provides the file name, line number, and the error text so the error list it produces can be used with Vim's quickfix functionality. More checks could be added, but it should find most of the obvious errors and warnings and some of the unusual messages in the SAS log. It can also be used outside of Vim to check SAS log files from the command line.

cklog was intended to run on Unix. It should run fine under Cygwin, as well (and the conversion to 4NT/TCC batch file is relatively trivial, using Cygwin grep, though I've tried it and for some reason, it has a hard time with overwritten error messages that SAS uses unless you turn that off in SAS--option NOOVP). Other scripting languages such as Perl, Python, or Ruby could also be used to do this task. Even SAS can do a reasonable job of log searching now that it can do Perl regular expressions in v9 (PRXPARSE, PRXMATCH, etc.).

grep -E (extended expressions) is the same as egrep on my Unix system (FreeBSD). The other grep options used are -n (give the line number), -H (give the file name), and -i (case-insensitive search). Extended regular expressions allow the use of the '|' ("or") operator so that multiple SAS log issue conditions can be searched for with one grep statement.


  #!/bin/sh
  
  # Check SAS log files for warning, errors, and other possible
  # problems. --KDN
  
  ME=`basename $0`
  
  case $# in
  
    0)
    echo -e "------------------------------------------------------------------------------"
    echo "$ME: check SAS log files for warnings, errors, and possible problems. "
    echo "Usage: $ME [FILE]"
    echo "   For example: $ME *.log will check all log files in the current directory."
    echo "                $ME mysas.log will just check just the mysas.log file."
    echo "by Kent Nassen, last update: 06JAN2009"
    echo "------------------------------------------------------------------------------"
    echo ""
    ;;
  
    *)
    for fname in $*
    do
      grep -E -H -n -i "warning:|error:|uninit| 0 obs|no obser|repeats of|not found\
  |not valid|invalid|syntax error|overwritten|converted|missing values were generated\
  |outside the axis range|duplicate by var|not created|contained a missing" ${fname}
    done
    ;;
  esac

Wrapper Script to Add Filename

A wrapper script to add the file name to log error list could be as simple as the following Bourne Shell script (which would become the grepprg program in CheckSASLog), where the "checksas" program operates on a file name given on the command line and returns a list of errors and warnings from the SAS log and the sed command adds the file name ($1) to the beginning of each line in the list of errors. '#' is used as a delimiter in the sed substitution command because $1 is apt to contain '/' characters if it includes a file path, saving us a lot of quoting. This is not needed if your external SAS log check program output includes only the file name and no path (but it doesn't hurt in either case). Again, this wrapper script can be written with other scripting languages.


  #!/bin/sh
  
  /usr/local/bin/checksas $1 | /usr/bin/sed -e "s#^#$1: #"

This page has been visited 8733+ times since May 3, 2009.