Automatización del borrado de ficheros

La automatización del borrado de ficheros es una tarea necesaria cuando, por ejemplo, estamos generando en un proceso planificado ficheros fechados periódicamente. Hacer exhaustivamente esta tarea es complicado y no siempre la realizamos por esa razón (yo al menos). Esta macro da solución a esa necesidad, os animo a usarla.

Lo primero que hacemos es crear un stream de datos, df, que recoge el resultado del comando ls. El parámetro F marca los directorios con una barra «/» al final, de forma que podemos eliminarlos de la salida del ls con un grep -v.

Usando df como entrada para nuestro paso data todo lo que queda es procesar las información que hemos generado con un scan, que divide la cadena por los espacios. Finalmente solo nos queda montar la fecha de creación del fichero teniendo en cuenta lo siguiente: Linux devuelve la fecha en dos formatos distintos dependiendo de si hace menos de 6 meses que se ha generado el fichero o si hace más. Este es un ejemplo:

Formatos de fecha en un ls

Finalmente, filtramos los registros que contienen los nombres de los ficheros que queremos eliminar: ficheros anteriores a 2020 y que no sean tablas SAS. Un call system ejemcutará un comando rm con cada registro resultante. Esta es la macro:

%macro borrado(ruta=,fecha_limite=);
    x "cd &ruta";
    filename df pipe "ls -lahF &ruta | grep -v /";

    data _null_;
        infile df;
        input todo $300.;
        format fecha date9. fichero $100.;
        dia = scan(todo,7," ");
        if index(scan(todo,8," "),":") then anyo=year(date());
           else anyo=input(put(scan(todo,8," "),$5.),8.);
        if compress(scan(todo,6," "))='Jan' then mes=1;
        else if compress(scan(todo,6," "))='Feb' then mes=2;
        else if compress(scan(todo,6," "))='Mar' then mes=3;
        else if compress(scan(todo,6," "))='Apr' then mes=4;
        else if compress(scan(todo,6," "))='May' then mes=5;
        else if compress(scan(todo,6," "))='Jun' then mes=6;
        else if compress(scan(todo,6," "))='Jul' then mes=7;
        else if compress(scan(todo,6," "))='Aug' then mes=8;
        else if compress(scan(todo,6," "))='Sep' then mes=9;
        else if compress(scan(todo,6," "))='Oct' then mes=10;
        else if compress(scan(todo,6," "))='Nov' then mes=11;
        else if compress(scan(todo,6," "))='Dec' then mes=12;
        fecha = mdy(mes,dia,anyo);
        if fecha > date() then fecha=intnx('year',fecha,-1,'S');
        fichero = compress(scan(todo,9," "),'*');
        if fecha < &fecha_limite and not index(fichero,'.sas7bdat');

        call system('rm '||fichero);
    run;
%mend;

%borrado(ruta=[rute],fecha_limite='1jan2020'd);

Gráficos de mapa con shapefiles: coronavirus

Vamos a tratar como usar shapefiles para dibujar mapas con SAS. Utilizaré para los ejemplos datos de la trágica lacra que nos asola estos días, la enfermedad del coronavirus.

Un shapefile es un formato vectorial de almacenamiento de información geográfica, es además un fichero multiarchivo porque requiere de varios ficheros para poder ser interpretado. Son varios los ficheros que pueden estar incluidos, pero al menos deben existir estos tres: .shp, .shx y .dbf.

SAS puede utilizar proc mapimport para importar el shapefile y convertirlo en tablas SAS que podremos tratar para construir gráficos. Además, en este caso, le vamos a asociar datos sobre el coronavirus consultados a fecha 4 de abril en la siguiente página de Sanidad.

proc gmap se encarga de dibujar el mapa que está definido en el parámetro map= y los datos para los gráficos o los colores de representación se encuentran informados por el contenido del parámetro data=. El statement choro (jocoso nombre para los peruanos) indica la variable que se utilizará para representar las distintas zonas del mapa con un color más o menos intenso según su valor relativo. La opción choro variable/discrete indica una variable no continua, por lo que los colores representados con colores distintos.

coronavirus1

filename file "&ruta/gadm36_ESP_1.shp" encoding="UTF-8";
proc mapimport datafile=file out = GDIS;
run;

proc sort data=GDIS out=COMUNIDAD (rename=(name_1 = comunidad)) nodupkey;
    by gid_1;
run;

/* Adecuamos la posición de las Islas Canarias para tener un mapa más grande */;
data GDIS;
    set GDIS;
    if name_1='Islas Canarias' then do;
        x = x + 20;
        y = y + 7;
    end;
run;

/* Añadimos la información del coronavirus */
data CORONAVIRUS;
    infile datalines dsd missover;
    format contagiados 8. comunidad $50. fallecidos 8.;
    input contagiados comunidad $ fallecidos;
    cards;
    36249, 'Comunidad de Madrid', 4723
    24734, 'Cataluña', 2508
    7875, 'Castilla y León', 723
    9324, 'Castilla-La Mancha', 989
    8187, 'País Vasco', 477
    7869, 'Andalucía', 426
    6901, 'Comunidad Valenciana', 571
    5625, 'Galicia', 159
    2972, 'Comunidad Foral de Navarra', 171
    3078, 'Aragón', 251
    2405, 'La Rioja', 128
    1979, 'Extremadura', 208
    1522, 'Principado de Asturias', 76
    1564, 'Islas Canarias', 78
    1384, 'Cantabria', 68
    1271, 'Islas Baleares', 71
    1188, 'Región de Murcia', 51
    152, 'Ceuta y Melilla', 3
    ;
run;

proc sql;
    create table COMUNIDAD2 as
    select a.*,
           b.contagiados,
           b.fallecidos
    from COMUNIDAD a
    left join CORONAVIRUS b
    on a.comunidad = b.comunidad;
quit;

proc gmap data=COMUNIDAD2 map=GDIS;
    title 'Contagiados por coronavirus en España';
    id gid_1;
    choro contagiados;
run;

Utilizar block en vez de choro representará los datos como columnas verticales dentro de cada zona. La opción legend=legend se utiliza para indicar que el color de las columnas y de la leyenda serán iguales.

coronavirus2

proc gmap data=COMUNIDAD2 map=GDIS;
    title 'Contagiados por coronavirus en España';
    id gid_1;
    block contagiados / legend=legend;
run;

Administración SAS: Detección sesiones pesadas

Este procedimiento sirve a aquellos administradores de un sistema SAS a determinar que sesión y que usuario pueden llegar a estar dando problemas al resto en un momento dado por estar consumiendo demasiado espacio en disco en el servidor.

Para poder ejecutarlo es necesario tener un usuario en el sistema operativo de la máquina SAS con permisos sudo. Este procedimiento está, por tanto, dirigido a máquinas Linux y se lo debo a mi compañero y amigo Christian.

Tras logarnos nos adjudicamos permisos sudo: sudo su -

sesiones1

Nos dirigimos a la ruta donde está la work, típicamente /opt/sas/saswork/. En esta carpeta se encuentran almacenadas las carpetas Linux que contienen todos los datos almacenados en todas las work de todas las sesiones SAS abiertas en ese momento por todos los usuarios en esa máquina. Listamos los nombres de esas work y determinamos cuales pueden ser más grandes y dar problemas, por ejemplo hacemos un grep para coger aquellas sesiones que están expresadas en Gb: du -sh * | grep G

sesiones2

Podemos ver que efectivamente las work están almacenadas en esa ruta de nuestro servidor si desde Enterprise Guide sacamos las propiedades de nuestra work con el botón derecho. La ruta que nos indica es exactamente esa.

sesiones3

Finalmente para identificar al usuario ‘infractor’ hacemos un grep del log de su sesión buscando su CLIENTMACINE, que no es más que su identificador de usuario. Se puede hacer también un grep a CLIENTUSERID que contiene el nombre del usuario:

grep CLIENTUSERID SASApp_WorkspaceServer_2018-11-07_sas_5889.log

En el caso de que estuviéramos buscando una sesión que se haya quedado colgada la tarea de identificarla es siempre muy fácil si los usuarios ejecutan un %put &sysjobid; porque reflejará el PID de esa tarea en el log y sabremos que tarea matar. Lo realmente idóneo es incluir esta instrucción en el autoexec.sas.

Automatización del borrado de tablas

En ocasiones queremos automatizar un proceso que genera tablas diaria o semanalmente y rápidamente se acumulan un montón de datos en nuestra librería de salida. Necesitamos gestionar esto, pero no podemos estar pendientes de ello. Utilizaremos entonces una macro que nos permite desatender el borrado con unas especificaciones y nos libere de ese trabajo.

Listado de tablas en la librería SASHELP

La macro está mínimamente parametrizada y utiliza el diccionario de tablas de SAS y el proc datasets. Tal como está definida realiza un borrado de la WORK de tablas de más de 3 meses de antigüedad. Evidentemente, no está pensada para usarla contra la WORK.

%macro borrado_historico;
    %let libreria = WORK;
    %let tablas = ;
    data _null_;
        call symput('fecha_limite',put(intnx('month',date(),-3,'S'),date9.));
    run;

    proc sql noprint; 
        select memname into :tablas separated by ' '
        from DICTIONARY.TABLES 
        where libname= "&libreria" and
              memname ne '_PRODSAVAIL' and
              datepart(crdate) < "&fecha_limite"d;
    quit;

    proc datasets lib=&libreria noprint;
        delete _PRODSAVAIL &tablas;
    run;
%mend;
%borrado_historico;

Primero realiza una consulta al diccionario de tablas (DICTIONARY.TABLES) por la fecha de creación de la tabla crdate (también se puede hacer por la fecha de modificación de la tabla: modate) y listo las tablas que serán borradas separadas por espacios para que puedan ser utilizadas directamente por el próximo paso. Añadir al filtro una condición substr(memname,1,9)='CAMPANAS_' nos permitirá borrar solo las tablas de determinado tipo, por supuesto.

En el proc datasets definimos la librería que queremos atacar y le pasamos al statement delete el listado de variables que previamente hemos calculado. Aquí he usado un pequeño truco para evitar que se produzca un error en el caso de que el listado de tablas esté vacío: y es que en el proc sql había excluido la tabla _PRODSAVAIL que añado ahora aquí para que siempre tenga algo que borrar.

¿Qué es la tabla _PRODSAVAIL? Pues contiene un listado de los módulos licenciados en SAS Enterprise Guide para que la herramienta lo pueda consultar rápidamente. Se genera automáticamente con cada sesión que se genera y no produce ningún efecto adverso en el Guide su borrado.

Comprobar si existe un campo en una tabla SAS

En ocasiones necesitamos saber si existe un fichero o una tabla y para ello utilizamos la función exist, pero ¿qué pasa si queremos saber si en la tabla que estamos procesando hay un determinado campo?

Es una situación menos habitual y lo malo es que no existe una función para evaluarlo directamente, así que nos toca remangarnos y usar un poco nuestra imaginación.

Existen varias soluciones para abordar esto, seguro que cada una más adecuada en según qué circunstancias. En este caso vamos a evaluar si en la tabla SASHELP.US_DATA tenemos el campo «capital» y os propongo varias soluciones:

La primera que se me ocurre es utilizando vectores:

%macro comprobar_variable1(campo_buscado=);
    %let dsid = %sysfunc(open(SASHELP.US_DATA));
    %let rc = %sysfunc(fetchobs(&dsid,1));
    %let r1 = %sysfunc(varnum(&dsid,&campo_buscado));
    %let r2 = %sysfunc(varnum(&dsid,&campo_buscado));
    %let resultado = %sysfunc(max(&r1>0,&r2>0));
    %put &=resultado;
    %let rc = %sysfunc(close(&dsid));
%mend;

%comprobar_variable1(campo_buscado=CAPITAL);
%comprobar_variable1(campo_buscado=region);

Otra solución aparentemente más sencilla de programar sería utilizando proc contents. A mi me resulta muy útil este procedimiento para resolver muchos problemas de automatización de procesos.

%macro comprobar_variable2(campo_buscado=);
    proc contents data=SASHELP.US_DATA out=VARIABLES (keep=name) noprint;
    run;

    proc sql noprint;
        select count(1) into :resultado
        from VARIABLES
        where name=upcase("&campo_buscado");
    quit;

    %put &=resultado;
%mend;
%comprobar_variable2(campo_buscado=CAPITAL);
%comprobar_variable2(campo_buscado=region);

Os propongo que enviéis alguna más que se os pueda ocurrir.