viernes, 27 de abril de 2007

Generar spools de más de 2 Gigas en Unix

Hay varias formas de generar spools con sql*plus desde Unix. La forma tradicional (o directa) es escribir un shell que realice lo siguiente:

-Invocar a sql*plus en modo embebido.
-Ejecutar spool y realizar el SELECT.
-Finalizar el spool con spool off.
-Salir de sql*plus.
-Zipear el archivo de salida (recomendado si se va a enviar a otro destino).

El problema que presenta este enfoque, es que en algunos sistemas operativos, puede existir el límite de 2 Gigas de tamaño para archivos, por lo cual nuestro script va a alimentar el spool hasta llegar a ese tamaño. En el mejor de los casos obtendremos un error inesperado, en otros quizás ni nos enteremos.

Una forma alternativa (y para mi gusto más elegante), es utilizar el comando mknod de Unix. Este comando, entre otras funcionalidades, permite crear un pipe de tipo FIFO que funciona como un dispositivo que utiliza el buffer del sistema operativo y permite conectarlo directamente con un programa de compresion como compress o gzip, evitando que se utilice el file system para almacenar el spool sin comprimir. También es posible conectarlo al programa split para que particione el archivo zip, si este continúa superando el límite máximo.

Básicamente las tareas que hay que escribir en nuestro shell son:
-Borrar pipes que hayan quedado anteriormente (puede fallar si ya existe)
-Crear el pipe
-Asignarle permisos al pipe (con chmod)
-Invocar en modo background al programa destino (el que va a recibir el spool). Este podría ser gzip, split, o ambos.
-Invocar a sqlplus para que realice el spool
-Borrar el pipe creado

Resulta poco intuitivo llamar primero al programa destino y luego en definitiva a quien genera el spool, pero veamos un ejemplo de código para visualizarlo más claramente.

Este script genera un spool y lo comprime en un zip:

1   #/bin/ksh
2 rm -f ${ARCHIVO}.pipe 2>/dev/null
3 mknod ${ARCHIVO}.pipe p
4 gzip -f < ${ARCHIVO}.pipe > ${ARCHIVO}.txt.Z 2>>${LOG} &
5 sqlplus -s usuario/password << EOF > /dev/null 2>> ${LOG}
6 spool ${ARCHIVO}.pipe
7 select * FROM user_objects;
8 spool off
9 EOF
10 rm -f ${ARCHIVO}.pipe 2>/dev/null
En la línea 2 eliminamos pipes que hayan podido quedar abiertos por alguna razón. De quedar abierto obtendríamos un error al crearlo de nuevo.

En la línea 3 creamos el pipe. El primer parámetro es el nombre y el segundo el tipo (p=FIFO)

En la línea 4 invocamos en background a gzip (notar el & al final de la linea). El pipe (el cual todavia no ha recibido datos), va a ser la entrada fuente para gzip. Luego gzip generará el archivo .txt.Z.

Las líneas 5 a 8 llaman a sqlplus y comienzan a enviar datos al pipe, luego gzip que esta corriendo en background zipea el contenido, sin necesidad de tener un archivo en file system.

La línea 10 elimina el pipe.

Si el archivo final .zip alcanza los 2 Gigas, vamos a tener el mismo problema, gzip no va a poder crearlo. Es necesario adicionarle el comando split y conectarlo con gzip para que parta el archivo en secciones de un tamaño manejable.
Para eso, hay que insertar entre la línea 3 y la 4 del script anterior, la siguiente línea:

3.5 split -b 1024m < ${ARCHIVO}.txt.Z &

Conviene leer primero la línea 4 y luego la 3.5, observar que ambos programas corren en background.

La línea 4 es la misma, genera el archivo .txt.Z.

La línea 3.5 recibe el archivo .txt.Z y lo divide en archivos de 1024 megas, el tamaño es en bytes (especificado por el parámetro b).

Sea cual sea el programa que genera el archivo, siempre podemos manipular con pipes los datos sin llegar a utilizar un archivo del file system para pasos intermedios.

Próximos pasos:
MAN del comando mknod
MAN del comando split

2 comentarios:

Luis Angel dijo...

Hola.
necesito ayuda para crear un archivo .bat que genere los txt de 100 archivos fmb que tengo en una carpeta, aunque puedo generar los txt desde oracle forms abriendo cada forma y generando el documento, quisiera crear el .bat por que tengo muchas mas formas a las que tengo que generarles su txt

gracias , espero puedas ayudarme

lfer dijo...

Create un .bat que invoque para cada fmb al programa frmcmp.exe:

C:\oracle\OraDeveloper\BIN>frmcmp.exe module=C:\Forms_ETL\etl_proceso.fmb LOGON=NO forms_doc=yes output_file=C:\Forms_ETL\etl_proceso_txt.fmb BATCH=YES Window_State=Minimize

El ejecutable frmcmp.exe viene generalmente en $ORACLE_HOME\bin

Espero que sirva