Tratamiento de Datasets en el Mainframe con JCL

Autor: Kujaku | El miércoles 24 de enero del 2007 @ 19:08.

El presente documento explicará, siguiendo el ejemplo del "Hola Mamones" de la entrega anterior, como realizar operaciones con ficheros o datasets. Antes que nada, voy a explicar lo que es un dataset:

Un dataset es una colección de registros/datos que los utiliza/genera cualquier programa. Viene a ser un "fichero". Hay dos tipos de datasets: Secuenciales, cuya información esta grabada consecutivamente y se lee de la misma manera (como un fichero cualquiera) y Particionados, cuya diferencia radica en que en este tipo de datasets hay uno o más miembros secuenciales en su interior (es como si fuera una carpeta que aloja varios secuenciales).

Basándonos en el JCL que escribimos en la otra entrega, que lo que hacía era sacar por la salida A los datos que le dábamos en el propio jobstream, haciendo uso de la DD *.

         1     1   2         3         4         5         6         7 7
----+----0----+6---0----+----0----+----0----+----0----+----0----+----0-2
//MAMONJOB JOB GODDESS,URD,CLASS=A,MSGCLASS=A,
//             MSGLEVEL=(1,1),REGION=256K
//PASO1   EXEC PGM=IEBGENER
//SYSUT1   DD  *
**********************************************************
*                                                        *
*                       Hola, Mamones!!                  *
*                                                        *
**********************************************************
/*
//SYSUT2   DD  SYSOUT=A
//SYSPRINT DD  SYSOUT=A
//SYSIN    DD  DUMMY
//

Lo que vamos a hacer ahora, es cambiar la ficha SUSUT1 con lo siguiente:

         1     1   2         3         4         5         6         7 7
----+----0----+6---0----+----0----+----0----+----0----+----0----+----0-2
//MAMONJOB JOB GODDESS,URD,CLASS=A,MSGCLASS=A,
//             MSGLEVEL=(1,1),REGION=256K
//PASO1   EXEC PGM=IEBGENER
//SYSUT1   DD  DSN=YGGDRASL.GODDESS.WISHLIB(BELDANDY),DISP=SHR
//SYSUT2   DD  SYSOUT=A
//SYSPRINT DD  SYSOUT=A
//SYSIN    DD  DUMMY
//

SYSUT1 tiene una definición que apunta a un DataSet Name (DSN) llamado YGGDRASL.GODDESS.WISHLIB, el cual es un dataset particionado porque tiene miembros en su interior, uno de los cuales se llama BELDANDY (El nombre real es Belldandy, pero por restricciones de nomenclatura (no pueden tener mas de 8 caracteres, se ha tenido que eliminar una "L" ;) ).

Lo que viene detrás es la DISPosición del dataset, es decir, el bloqueo que vamos a realizar sobre ese fichero cuando ejecutemos el job y accedamos a él. DISP puede tener hasta tres sub-parámetros.

El primero de los sub-parámetros es el bloqueo del dataset en la ejecución del job, existiendo las siguientes opciones:

  • DISP=SHR, para un dataset que existe y que solo lo voy a leer (SHR de Shared es porque como solo lo leo, muchos otros pueden leerlo también sin joderles la marrana).

  • DISP=OLD, para un dataset que existe y que voy a reescribir / cambiar / borrar.

  • DISP=MOD, para un dataset al que le quiero añadir nueva información al final (no cambiando la existente).

  • DISP=NEW, para un dataset que crearé nuevo en ese paso.

En las ultimas 3 disposiciones, el bloqueo será exclusivo: mientras mi job este en ejecución, ninguna otra persona podrá acceder a ese dataset.

El segundo sub-parámetro sirve para saber que hacer con ese dataset cuando el paso termine. Puede tener los siguientes valores:

  • KEEP: Deja el dataset como esta.
  • CATLG: Deja el dataset como esta y lo cataloga en el catalogo correspondiente.
  • PASS: Deja el fichero como esta, pero la decisión de que hacer con él lo decidirá el siguiente paso.
  • DELETE: Borra el dataset del disco y del catalogo.

Y el tercer y último sub-parámetro se utiliza para saber que hacer con el fichero si el job casca estrepitosamente. Tiene los mismos valores que el segundo.

Por ejemplo, si a un dataset le ponemos DISP=(NEW,CATG,DELETE), le estamos diciendo que ese paso crea el fichero, y si el paso del job termina bien, lo cataloga en el SO, pero si el job casca, lo borra. Fácil, ¿no?

¿Que se consigue con este JCL? Pues es imprimir el miembro BELDANDY que esta dentro del dataset YGGDRASL.GODDESS.WISHLIB.

Pues vamos más allá. Si quiero copiar el miembro BELDANDY a otro dataset, ¿como lo hago? Pues sencillo, basta con modificar la ficha DD relacionada con la salida, es decir, SYSUT2, y especificar ahí el dataset de destino. La cosa quedaría entonces así:

         1     1   2         3         4         5         6         7 7
----+----0----+6---0----+----0----+----0----+----0----+----0----+----0-2
//MAMONJOB JOB GODDESS,URD,CLASS=A,MSGCLASS=A,
//             MSGLEVEL=(1,1),REGION=256K
//PASO1   EXEC PGM=IEBGENER
//SYSUT1   DD  DSN=YGGDRASL.GODDESS.WISHLIB(BELDANDY),DISP=SHR
//SYSUT2   DD  DSN=YGGDRASL.HUMANS.KEIICHI,DISP=(NEW,CATLG,DELETE)
//SYSPRINT DD  SYSOUT=A
//SYSIN    DD  DUMMY
//

Esto hará que se copie el miembro BELDANDY en un nuevo dataset, llamado YGGDRASL.HUMANS.KEIICHI. Este dataset es secuencial, ya que no tiene miembros, es decir, será el propio dataset el contenedor de los datos. Ojo, si ese dataset ya existe, el JCL daría un error, por lo que habría que modificar la disposición por la adecuada (DISP=MOD, u OLD). Pero continuamos entrando un poco más en el ajo.

El job tal y como está funciona, ya que el propio IEBGENER exportará la información en la creación de ese nuevo dataset. Pero si queremos especificar la forma y donde grabar, debemos dar mas datos al JCL.

Antiguamente, el sistema de almacenamiento era extremadamente caro, por lo que se debía aprovechar hasta el último bit y no reservar más espacio del necesario. Así que cuando se creaba un dataset, se solía dar la información estimada de lo que iba a ocupar, y se reservaba ese espacio para el dataset. Así que se añadía un parámetro adicional, llamado SPACE que tenía varios sub-parámetros: SPACE=(Unidad (Primaria, Secundaria, Directorio)).

Por Unidad, entendemos la métrica que utilizaremos. Puede ser TRK (Tracks o Pistas), CYL (Cilindros), o puede ser un número natural que constituya los Bytes. Cabe decir que según el modelo de disco que estemos utilizando, el tamaño en bytes de un cilindro o una pista puede variar considerablemente. Por ejemplo, en un disco IBM 3330 de la época, había 13.030 bytes por pista, y tenía 19 pistas por cilindro. Por lo tanto, un cilindro tenía 247.570 bytes. En cambio, en los nuevos discos IBM 3390, tiene 56.664 bytes por pista y 15 pistas por cilindro, por lo que un cilindro tiene 849.960 bytes. El sub-parámetro Primario, dice cuanto se va a reservar en lo que marque la Unidad, y el sub-parámetro Secundario, dice cuanto más se va a reservar si se llena la zona Primaria.

Por último, el sub-parámetro Directorio, nos dice cuantos bloques de 256 bytes reservaremos para los índices en caso que nuestro dataset sea particionado (es decir, con miembros en su interior). Como esto puede que sea lioso de entender, con unos ejemplos quizás se vea mejor:

  • SPACE=(TRK,10): Esto significa que reservaremos 10 pistas para nuestro dataset.

  • SPACE=(CYL,(10,2)): Con esto, estamos diciendo que se reserve un espacio de 10 cilindros y 2 cilindros como extensión secundaria.

¿Que significa esto? Que si llenamos los 10 cilindros, nos extenderá el tamaño 2 cilindros más. ¿Y si los llenamos también? Pues se extenderá el espacio otros 2 cilindros más, así hasta un máximo de 15 veces, y llegado a ese punto, el dataset quedará lleno completamente y no se extenderá más.

  • SPACE=(4096,(100,50)): Esto es que queremos que reserve 100 bloques de 4096 bytes de tamaño, llenados los cuales, hará que se extienda el tamaño otros 50 bloques mas hasta un máximo de 15 extensiones.

  • SPACE=(CYL,(10,2,5)): Esto solo tiene sentido si estamos creando un dataset particionado: Queremos 10 cilindros de espacio, con 2 cilindros de extensión (hasta 15 veces más de extensión) y 5 bloques de 512 bytes que guardarán la tabla de índices para los miembros. Si ponemos mas bloques, mas índices entrarán.

Como cada bloque puede guardar la información de direccionamiento de al menos 5 miembros, con ese 5 le estamos diciendo que nuestro dataset puede almacenar hasta un máximo de 25 miembros en su interior. Suele ser recomendable dar más bloques por si acaso, no vaya a ser que no puedas crear más miembros y tengas el espacio reservado en cilindros a medio llenar, con lo que todo ese espacio estaría desaprovechado.

Así pues, una vez introducido este parámetro, el JCL nos quedaría así:

         1     1   2         3         4         5         6         7 7
----+----0----+6---0----+----0----+----0----+----0----+----0----+----0-2
//MAMONJOB JOB GODDESS,URD,CLASS=A,MSGCLASS=A,
//             MSGLEVEL=(1,1),REGION=256K
//PASO1   EXEC PGM=IEBGENER
//SYSUT1   DD  DSN=YGGDRASL.GODDESS.WISHLIB(BELDANDY),DISP=SHR
//SYSUT2   DD  DSN=YGGDRASL.HUMANS.KEIICHI,DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(1,1))
//SYSPRINT DD  SYSOUT=A
//SYSIN    DD  DUMMY
//

Claro, la sentencia SPACE tiene sentido para dataset que vaya a ser guardad en disco. Para la información que vayamos a guardar en cinta, esto no se aplica. Vale, hasta aquí sabemos CUANTO podemos llenar un dataset. Pero claro, falta saber DONDE lo grabamos. Para eso tenemos otro parámetro: UNIT.

Especificando UNIT, introduciremos el modelo del disco o cinta donde se grabará la información, por lo que deberemos conocer que instalación mainframe tenemos y que dispositivos tiene conectados.

Por ejemplo, si ponemos UNIT=3390, ese dataset lo grabaremos en un disco modelo 3390. Si quisiéramos almacenarlo en cinta, deberíamos saber que modelo de cintas tiene nuestra instalación, de tal forma que si tuviéramos una IBM 3490, deberíamos poner UNIT=3490 en nuestro job.

Afortunadamente, para que no tengamos que saber los detalles, marcas y modelos de nuestro almacenamiento, IBM creo unos nombres llamados Esotéricos que lo que hacen es, en tiempo de definición de la configuración IODF, asignar un nombre conocido por todos a un modelo de dispositivo concreto, y así no tenemos por que conocer las particularidades de la instalación.

El SO ya proporciona un esotérico por defecto llamado SYSDA, que hace referencia a cualquier disco que tengamos en la instalación. Así que poniendo UNIT=SYSDA, le decimos a nuestro JCL que ese dataset irá a parar a disco.

Yo también puedo definir otros esotéricos en tiempo de configuración. He visto JCLs en otras instalaciones que si quieres grabar en cinta de carrete de cinta de los antiguos, pones UNIT=CARRETE, y en cambio, si quieres grabarlo en cintas mas nuevas, pues pones UNIT=CARTUCHO. Esto funciona porque alguien habrá decido que una unidad 3420 o 3422 que es de carrete, tenga como esotérico CARRETE, y lo mismo con el CARTUCHO, que tendrá probablemente unas definiciones de unidades de cartuchos 3480 o 3490.

Pero volviendo al JCL, como queremos grabar nuestro dataset a disco, le añadimos el UNIT, con lo que nos quedaría el tema así:

         1     1   2         3         4         5         6         7 7
----+----0----+6---0----+----0----+----0----+----0----+----0----+----0-2
//MAMONJOB JOB GODDESS,URD,CLASS=A,MSGCLASS=A,
//             MSGLEVEL=(1,1),REGION=256K
//PASO1   EXEC PGM=IEBGENER
//SYSUT1   DD  DSN=YGGDRASL.GODDESS.WISHLIB(BELDANDY),DISP=SHR
//SYSUT2   DD  DSN=YGGDRASL.HUMANS.KEIICHI,DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(1,1)),UNIT=SYSDA
//SYSPRINT DD  SYSOUT=A
//SYSIN    DD  DUMMY
//

Con esto, ya podemos estar seguros de que nuestro dataset YGGDRASL.HUMANS.KEIICHI se va a grabar a disco. Pero... ¿A qué disco? Si no ponemos nada, el SO elegirá uno al azar, pero bueno, lo ideal es saber donde guardamos nuestro dataset. Así que se añade un parámetro adicional, que es el VOL=SER=Nombre, siendo Nombre un nombre (valga la redundancia) alfanumérico de 6 caracteres (no 8 como pudieras imaginar) que identifica al disco (VOLume SERial number). Este nombre se le da al disco en tiempo de inicialización y debe ser único en la instalación. Así que suponiendo que tengamos un disco llamado WORK01, y queremos que nuestro dataset se grabe ahí, el JCL quedaría como sigue:

         1     1   2         3         4         5         6         7 7
----+----0----+6---0----+----0----+----0----+----0----+----0----+----0-2
//MAMONJOB JOB GODDESS,URD,CLASS=A,MSGCLASS=A,
//             MSGLEVEL=(1,1),REGION=256K
//PASO1   EXEC PGM=IEBGENER
//SYSUT1   DD  DSN=YGGDRASL.GODDESS.WISHLIB(BELDANDY),DISP=SHR
//SYSUT2   DD  DSN=YGGDRASL.HUMANS.KEIICHI,DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(1,1)),UNIT=SYSDA,
//             VOL=SER=WORK01
//SYSPRINT DD  SYSOUT=A
//SYSIN    DD  DUMMY
//

El VOL=SER no sólo identifica discos, sino también las cintas, de modo que si quisiéramos grabar en una cinta concreta el dataset, con poner el nombre de la cinta en cuestión, la unidad de cintas mostraría al operador un mensaje luminoso que le indicaría que debe montar la cinta con la etiqueta que le hemos asignado.

Con esto, nuestro JCL está listo para copiar la información del miembro BELDANDY a nuestro nuevo dataset secuencial. Si no añadimos más parámetros descriptivos, IEBGENER se basará en grabar la información en el nuevo dataset, siguiendo el patrón del miembro BELDANDY.

Pero, si por circunstancias del destino, queremos ser todavía mas descriptivos en el nuevo dataset en la disposición de los datos, utilizaremos un nuevo parámetro llamado DCB (Dataset Control Block). Esto indicará COMO grabar los datos.

En el DCB especificaremos 3 cosas: Formato del registro (RECFM, RECord ForMat), El tamaño del registro lógico (LRECL Logical RECord Length) y el tamaño del bloque (BLKSIZE, BLocK SIZE).

Usando un ejemplo, si decimos que tenemos un dataset con DCB=(RECFM=FB,LRECL=80,BLKSIZE=5600), le estamos diciendo que nuestro dataset tiene un tamaño de registro Fijo distribuido en Bloques (FB), cuya longitud de registro lógico es de 80 bytes y que el tamaño del bloque utilizado es de 5600 bytes.

El formato puede ser Fijo (F), Variable (V) y no especificado (U). Así pues, si queremos que nuestro nuevo dataset tenga ese tipo de disposición, añadimos al JCL esa línea:

         1     1   2         3         4         5         6         7 7
----+----0----+6---0----+----0----+----0----+----0----+----0----+----0-2
//MAMONJOB JOB GODDESS,URD,CLASS=A,MSGCLASS=A,
//             MSGLEVEL=(1,1),REGION=256K
//PASO1   EXEC PGM=IEBGENER
//SYSUT1   DD  DSN=YGGDRASL.GODDESS.WISHLIB(BELDANDY),DISP=SHR
//SYSUT2   DD  DSN=YGGDRASL.HUMANS.KEIICHI,DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(1,1)),UNIT=SYSDA,
//             VOL=SER=WORK01,
//             DCB=(RECFM=FB,LRECL=80,BLKSIZE=5600) 
//SYSPRINT DD  SYSOUT=A
//SYSIN    DD  DUMMY
//

Con esto, hay que tener cuidado: Si el Dataset de origen (YGGDRASL. GODDESS. WISHLIST (BELDANDY)) no tiene el mismo formato de registro, dará un error o los datos de destino se pueden corromper en la copia, ya que al ser los registros secuenciales, un bit de más o de menos en el registro podría mover toda la estructura.

Otro ejemplo: ¿Qué pasa si quiero copiar un dataset secuencial (YGGDRASL.HUMANS.KEIICHI) a un miembro nuevo (KEIICHI) de un dataset particionado, también nuevo (YGGDRASL.GODDESS.BELDANDY), y borrar el dataset secuencial antiguo?

Pues que se modifica un poquito el JCL:

         1     1   2         3         4         5         6         7 7
----+----0----+6---0----+----0----+----0----+----0----+----0----+----0-2
//COPIADS  JOB GODDESS,URD,CLASS=A,MSGCLASS=A,
//             MSGLEVEL=(1,1),REGION=256K
//PASO1   EXEC PGM=IEBGENER
//SYSUT1   DD  DSN=YGGDRASL.HUMANS.KEIICHI,DISP=(OLD,DELETE,KEEP)
//SYSUT2   DD  DSN=YGGDRASL.GODDESS.BELDANDY(KEIICHI),
//             DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(10,1,10)),UNIT=SYSDA,
//             VOL=SER=WORK01,
//             DCB=YGGDRASL.GODDESS.URD 
//SYSPRINT DD  SYSOUT=A
//SYSIN    DD  DUMMY
//

¿Qué hace este job? Primero, en SYSUT1, pondremos la información que queremos leer, que es un dataset secuencial. Importante ver la DISPosición, que significa que es un dataset que existe y que vamos a modificar porque luego lo borraremos (OLD), y si la copia sale bien, nos lo cargaremos (DELETE), pero que si algo ocurre con el job, que lo deje como está (KEEP).

No se si os habéis dado cuenta que aquí no he dicho ni UNIT, ni VOL=SER, ni nada por el estilo. No los he dicho porque asumo que el dataset YGGDRASL.HUMANS.KEIICHI es un dataset que esta catalogado (porque en el JCL anterior, le hemos dicho que nos los catalogue con un CATLG). Si no lo estuviera, habría que dar los datos de donde se encuentra.

En SYSUT2, ponemos el nuevo dataset (NEW) y entre paréntesis, el nombre del miembro. Le decimos que si sale bien, nos lo catalogue (CATLG) y que si sale mal, que lo borre (DELETE).

Además, le damos un espacio de 10 cilindros de partida, los cuales se pueden ampliar a un cilindro más si esos 10 se llenan y así hasta 15 veces, y le damos un tamaño para los miembros de 10, es decir, que se pueden guardar hasta 50 miembros además del nuevo miembro a crear.

También, le decimos que ese dataset particionado nos lo deje en el volumen de disco WORK01 y que la disposición de los registros, la obtenga usando como plantilla el dataset particionado YGGDRASL.GODDESS.URD que fue creado hace tiempo (así te evitas poner el RECFM, LRECL y BLKSIZE). Se entiende, ¿no?

Pues si volvemos al primer ejemplo de todos, podemos hacer que un dataset lo llenemos de información proveniente del propio JCL, como por ejemplo, almacenar la información del banner Hola Mamones en un dataset. Para ello, basta con lanzar el siguiente job:

         1     1   2         3         4         5         6         7 7
----+----0----+6---0----+----0----+----0----+----0----+----0----+----0-2
//MAMONJOB JOB GODDESS,URD,CLASS=A,MSGCLASS=A,
//             MSGLEVEL=(1,1),REGION=256K
//PASO1   EXEC PGM=IEBGENER
//SYSUT1   DD  *
**********************************************************
*                                                        *
*                       Hola, Mamones!!                  *
*                                                        *
**********************************************************
/*
//SYSUT2   DD  DSN=YGGDRASL.PRUEBAS.HOLA,DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(1,1)),UNIT=SYSDA,
//             VOL=SER=WORK01,
//             DCB=(RECFM=FB,LRECL=80,BLKSIZE=5600) 
//SYSPRINT DD  SYSOUT=A
//SYSIN    DD  DUMMY
//

Ester job, dejará el banner en un dataset secuencial nuevo que se denominará YGGDRASL.PRUEBAS.HOLA.

Por último, comentar que la gran mayoría de los datasets secuenciales (sobre todo los que se usan para guardar JCLs y demás) suelen se de tipo FB (bloque fijo) con LRECL=80, o dicho de otro modo, que cada línea o registro tiene una longitud de 80 caracteres, la misma longitud que las antiguas fichas perforadas. Como veréis, todo esto tiene sentido, ¿verdad?.

Si esto os ha quedado claro, ya estáis en condiciones de lidiar con los datasets dentro del mainframe.

El siguiente artículo seguirá explicando temas de datasets y otras UTILITIES que nos pueden venir bien para sacar mejor partido a nuestros datos.

Comentarios