4. Imprimiendo la Salida

Una de las cosas más corrientes que realizan las acciones es sacar o imprimir parte o toda la entrada. Para salida simple, utilice la sentencia print. Para un formateo más elegante utilice la sentencia printf. Ambas son descritas en este capítulo.

La sentencia print

La sentencia print realiza la salida con un formato estandarizado y simple. Tú especificas solamente las cadenas o números que van a ser impresos, en una lista separada por comas. Ellos son impresos separados por espacios en blanco, seguidos por un carácter newline o retorno de carro. La sentencia presenta la siguiente forma:

print item1, item2, …

La lista completa de items podría ser opcionalmente encerrada entre paréntesis. Los paréntesis son necesarios si algunos de las expresiones items utiliza un operador relacional; por que si no, podría ser confundido con un redireccionamiento (ver la sección Redireccionando la Salida de print y printf). Los operados relacionales son ‘==’, ‘!=’, ‘<’, ‘>’, ‘>=’, ‘<=’, ‘~’ y ‘!~’ (ver sección Expresiones de Comparación)

Los items impresos pueden ser cadenas constantes o números, campos del registro actual (tal y como $1), variables, o cualquier expresión awk. La sentencia print es completamente general para procesar cuales quiera valores a imprimir. Con una excepción (ver sección Separadores de la Salida), lo que no puedes hacer es especificar como imprimirlos – cuantas columnas usar, si se usará notación exponencial o no, y otras. Para eso, necesitas la sentencia printf (ver la sección Uso de sentencias printf para una impresión más elegante).

La sentencia simple ‘print’ sin ningún item es equivalente a ‘print $0’: imprime el registro actual entero. Para imprimir una línea en blanco, use ‘print “”’, donde “” es la cadena vacía o nula.

Para imprimir una parte de texto fija, utilice una constante cadena tal y como “Hola chico” como item. Si olvidas usar los caracteres comillas dobles, tu texto será interpretado como una expresión awk, y probablemente obtendrás un error. Ten en cuenta que se imprime un espacio para separar cuales quiera dos items.

Muy a menudo, cada sentencia print produce una línea de salida. Pero no está limitado a una línea. Si el valor de un item es una cadena que contiene un newline, el carácter newline se imprime con el resto de la cadena. Un único print podría imprimir cualquier número de líneas.

Ejemplos de sentencias print

Aquí aparece un ejemplo de impresión de una cadena que contiene caracteres de retorno de carro o nueva línea empotrados:

awk 'BEGIN { print "línea uno\nlínea dos\nlínea tres" }'

produce una salida como esta:

línea uno

línea dos

línea tres

Aquí tienes un ejemplo que imprime los dos primeros campos de cada registro de entrada, con un espacio entre ellos:           

awk '{ print $1, $2 }' inventario

La salida presentaría la siguiente forma:

Jan 13

Feb 15

Mar 15

Un error común en el uso de la sentencia print es omitir la coma entre dos items. Esto a menudo produce el efecto de imprimir los dos valores sin separar por un espacio en blanco. La razón de esto es que la yustaposición de dos expresiones tipo carácter en awk realiza la concatenación de ellas. Por ejemplo, sin la coma:

awk '{ print $1 $2 }' inventario

imprime:

Jan13

Feb15

Mar15

Debido a que esta salida no le resultaría informativa a la gente que no conociese el contenido del fichero “invertario”, una línea de cabecera al principio aclararía mucho dicha salida. Añadamos pues, una cabecera a nuestro listado de meses ($1) y canastas verdes enviadas ($2). Nosotros hacemos esto usando el patrón BEGIN (ver la sección Los Patrones Especiales BEGIN y END) para hacer que la cabecera sea impresa una sola vez:

awk 'BEGIN {  print "Meses Canastas"

              print "----- --------" }

           {  print $1, $2 }' inventario

¿Has conseguido averiguar qué ocurre? El programa imprime lo siguiente:

Meses Canastas

----- --------

Jan 13

Feb 15

Mar 15

¡Las cabeceras y las líneas de detalle no están alineadas! Podemos arreglar esto imprimiendo algunos espacios en blanco entre los dos campos:

awk 'BEGIN { print "Meses Canastas"

             print "----- --------" }

           { print $1, "     ", $2 }' inventario

Puedes imaginar que esta forma de alineación de columnas se puede volver realmente complicado cuando tienes que cuadrar muchas columnas. Contar los espacios para dos o tres columnas puede ser simple, pero con más de tres columnas te puedes perder bastante facílmente. Este es el motivo por el que se creó la sentencia printf (Ver la sección Uso de sentencias printf para una impresión más elegante); una de sus especialidades es la de alinear las columnas de datos.

Separadores de la Salida

Como se mencionó anteriormente, una sentencia print contiene una lista de items, separados por comas. En la salida, los items son separados normalmente por simples espacios en blanco. Pero esto no tiene por que ser así; el espacio es solamente el separador por defecto. Puedes especificar cualquier cadena de caracteres para usarla como el separador de campos de salida fijando la variable implícita OFS. El valor inicial de esta variable es la cadena “ ”.

La salida completa de una sentencia print se le llama registro de salida. Cada sentencia print imprime un registro de salida y después imprime una cadena llamada el separador de registros de salida. La variable implícita ORS determina esta cadena. El valor inicial de la variable es la cadena ‘\n’ o lo que es lo mismo el carácter newline, por lo que, normalmente cada sentencia print crea una línea distinta.

Puedes cambiar como se separan los campos y registros de salida, asignándoles nuevos valores a las variables OFS y/o ORS. La sitio normal para hacer esto es en la regla BEGIN (Ver la sección Los Patrones Especiales BEGIN y END), de modo que tomen sus valores antes de que se procese ninguna entrada. También podrías hacer esto con asignaciones en la línea de comando, antes de los nombres de tus ficheros de entrada.

El siguiente ejemplo imprime el primer y segundo campo de cada registro de entrada separados por un punto y coma, añadiéndole una línea en blanco adicional después de cada registro en la salida:

awk 'BEGIN { OFS = ";"; ORS = "\n\n" }

           { print $1, $2 }'  Lista–BBS

Si el valor de ORS no contiene un carácter newline, toda tu salida se generará a una única línea, a menos que pongas saltos de línea de alguna otra forma.

Uso de sentencias printf para una impresión más elegante

Si quieres un control más preciso sobre el formato de la salida del que te da la sentencia print, utilice printf. Con printf puedes especificar el ancho a utilizar con cada item, y puedes seleccionar entre varias elecciones de estilo para números (tales como qué “radix” usar, si imprimir un exponente, si imprimir un signo, y cuando dígitos imprimir después del punto decimal). Haces esto especificando una cadena, llamada la cadena de formato, la cual controla como y donde imprimir los otros argumentos.

Introducción a la sentencia printf

La sentencia printf presenta la siguiente sintáxis:

printf formato, item1, item2, …

La lista completa de items podría ser opcionalmente encerrada entre paréntesis. Los paréntesis son necesarios si cualquiera de las expresiones item utiliza un operador relacional, de otra forma podría ser confundido con una redirección (Ver la sección Redireccionando la Salida de print y printf). Los operadores relacionales son ‘==’, ‘!=’, ‘<’, ‘>’, ‘>=’, ‘<=’, ‘~’ y ‘!~’ (Ver la sección Expresiones de Comparación)

La diferencia entre print y printf es el argumento formato. Éste es una expresión cuyo valor se toma como una cadena; su función es especificar como se deben imprimir cada uno de los otros argumentos. Se le llama la cadena de formato.

La cadena de formato es esencialmente que la que se usa para la función printf de la librería de C. La mayoría del formato es texto para que sea impreso literalmente. Pero en medio del texto que debe ser impreso literalmente aparecen especificadores de formato, uno por item. Cada especificador de formato especifica el formato de salida del item o parámetro correspondiente

La sentencia printf no añade automáticamente un carácter newline a su salida. No imprime ninguna otra cosa que lo especificado en el formato. De forma que si quieres un carácter newline, debes incluir uno en el formato. Las variables de separación de la salida OFS y ORS no tienen efecto sobre las sentencias printf.

Letras para el control de formato

Un especificador de formato comienza con el carácter ‘%’ y acaba con una letra de control de formato, y le indica a la sentencia printf como imprimir el item correspondiente (Si realmente quieres imprimir un carácter ‘%’, escribe ‘%%’). La letra de control de formato especifica el tipo de valor a imprimir. El resto del especificador de formato está compuesto de modificadores opcionales los cuales son parámetros tales como el ancho de campo a usar.

Aquí tienes una lista de letras de control de formato:

‘c’

Esto imprime un número como un carácter ASCII. Por lo que, `printf "%c", 65' imprimiría la letra ‘A’. La salida para un valor cadena es el primer carácter de la cadena.

‘d’

Esto imprime un entero decimal.

‘i’

Esto también imprime un entero decimal.

‘e’

Esto imprime un número en notación científica (exponencial). Por ejemplo,

printf "%4.3e", 1950

imprime ‘1.950e+03’, con un total de 4 cifras significativas de las cuales 3 siguen al punto decimal. Los modificadores ‘4.3’ son descritos más abajo.

‘f’

Esto imprime un número en notación punto flotante.

‘g’

Esto imprime en notación científica o en notación punto flotante, la que quiera que sea más corta.

‘o’

Esto imprime un entero octal sin signo.

‘s’

Esto imprime una cadena.

‘x’

Esto imprime un entero hexadecimal sin signo.

‘X’

Esto imprime un entero hexadecimal sin signo. Sin embargo, para los valores entre 10 y 15, utiliza las letras desde la ‘A’ a la ‘F’ en lugar de esas mismas letras pero en minúsculas.

‘%’

Esta no es realmente una letra de control de formato. Pero tiene un significado especial cuando se usa después de un ‘%’: la secuencia ‘%%’ imprime el carácter ‘%’. No consume ni necesita ningún item o argumento correspondiente.

 

Modificadores para los formatos de printf

Una especificación de formato también puede incluir modificadores que controlan como son impresos los valores de los items y cuanto espacio ocuparán. Los modificadores vienen entre el signo ‘%’ y la letra de control de formato. Aquí están los posibles modificadores, en el orden en el cual podrían aparecer:

‘–’

El signo menos, usado antes del modificador de ancho, especifica que se justifique a la izquierda el argumento dentro de la anchura especificada. Normalmente el argumento se ajusta a la derecha dentro del ancho especificado. Por lo que,

printf "%–4s", "foo"

imprime ‘foo ’.

ancho

Este es un número que representa el ancho deseado para un campo. La inserción de cualquier número entre el signo ‘%’ y el carácter de control de formato fuerza a que el campo se expanda a este ancho. El modo por defecto para hacer esto es rellenando con espacios en blanco por la izquierda. Por ejemplo,

printf "%4s", "foo"

imprime ‘ foo’.

El valor de ancho es un ancho mínimo, no un máximo. Si el valor del item requiere más de ancho caracteres, podrá ser tan ancho como necesite. Por lo que,

printf "%4s", "foobar"

imprime ‘foobar’. Precediendo el ancho con un signo menos hace que la salida sea rellena con espacios en blanco por la derecha, en lugar de por la izquierda.

‘.precisión

Este es un número que especifica la precisión que se debe usar cuando se imprima. Esto especifica el número de dígitos que quieres imprimir a la derecha del punto decimal. Para una cadena, especifica el número máximo de caracteres que se imprimirán de dicha cadena.

La  capacidad de ancho y precisión dinámicos de la sentencia printf de la librería de C (por ejemplo, "%*.*s") todavía no está soportada. Sin embargo, se puede simular facilmente usando la concatenación para construir dinámicamente la cadena de formato.

Ejemplos de Uso de printf

Aquí tienes como usar printf para presentar una tabla alineada:

awk '{ printf "%–10s %s\n", $1, $2 }' Lista–BBS

imprime los nombres de los bulletin boards ($1) del fichero ‘Lista–BBS’ como una cadena de 10 caracteres justificados a la izquierda. También imprime los números de teléfono ($2) a continación en la línea. Esto produce una tabla alineada de dos columnas de nombres y números de teléfonos:

aardvark   555–5553

alpo–net   555–3412

barfly     555–7685

bites      555–1675

camelot    555–0542

core       555–2912

fooey      555–1234

foot       555–6699

macfoo     555–6480

sdace      555–3430

sabafoo    555–2127

¿Te diste cuenta de que no especificamos que se imprimiesen los números de teléfono como números? Tienen que ser impresos como cadenas debido a que están separados por medio con el guión. Este guión podría ser interpretado como un signo menos si hubiesemos intentado imprimir los números de teléfono como números. Esto nos hubieses producido unos resultados confusos y erróneos.

No hemos especifado un ancho para los números de teléfono porque son lo último que se imprimirá en cada línea. No necesitamos poner espacios después de ellos.

Podríamos haber hecho nuestra tabla más elegante incluso añadiéndole cabeceras encima de cada una de las columnas. Para hacer esto, usa el patrón BEGIN(Ver la sección Los Patrones Especiales BEGIN y END) para hacer que se imprima la cabecera solamente una vez, al principio del programa awk:

awk 'BEGIN { print "Nombre    Número"

             print "------    ------" }

     { printf "%–10s %s\n", $1, $2 }' Lista–BBS

¿Te diste cuenta de que mezclamos las sentencias print y printf en el ejemplo anterior? Podríamos haber utilizado solamente sentencias printf para obtener el mismo resultado:

awk 'BEGIN { printf "%–10s %s\n", "Name", "Number"

             printf "%–10s %s\n", "----", "------" }

     { printf "%–10s %s\n", $1, $2 }' Lista–BBS

Poniendo cada cabecera de columna con la misma especificación de formato usada para los elementos, nos hemos asegurado que las cabeceras tendrán la misma alineación que las columnas. El hecho de que la misma especificación de formato se use tres veces, puede ser resumido almacenando dicha especificación de formato en una variable, tal y como sigue:

awk 'BEGIN { format = "%–10s %s\n"

             printf format, "Name", "Number"

             printf format, "----", "------" }

     { printf format, $1, $2 }' Lista–BBS

Redireccionando la Salida de print y printf

Hasta ahora hemos estado tratando solamente con salida que imprime en la salida estándar, normalmente tu terminal. Se les puede decir tanto a print como a printf  que envíen su salida a otros sitios. Esto recibe el nombre de redirección.

Una redirección aparece después de la sentencia print o printf. Las redirecciones en awk son escritas del mismo modo que se hacen las redirecciones en los comandos de la shell, excepto que son escritas dentro del programa awk.

Redireccionando la Salida a Ficheros y Pipes

Aquí aparecen las tres formas de la redirección de la salida. Ellas se muestran todas para la sentencia print, pero funcionan de forma idéntica para printf.

print items > fichero–salida

Este tipo de redirección imprime los items en el fichero de salida fichero–salida. El nombre del fichero fichero–salida puede ser cualquier expresión. Su valor se convierte a cadena y después es usada como nombre de fichero. (Ver la sección 7. Acciones: Expresiones).

Cuando se usa este tipo de redirección, el fichero–salida es eliminado antes de que se escriba en él la primera salida. Las escrituras siguientes no borrarán el fichero–salida, pero sí añadirán a él. Si fichero–salida no existe entonces será creado.

Por ejemplo, aquí aparece como un programa awk puede escribir una lista de nombres BBS a un fichero ‘lista–nombres’ y una lista de números de teléfonos a un fichero ‘lista–teléfonos’. Cada fichero de salida contiene un nombre o número por línea.

awk '{ print $2 > "lista–teléfonos"

       print $1 > "lista–nombres" }' Lista–BBS

print items >> fichero–salida

Este tipo de redirección imprime los items en el fichero de salida fichero–salida. La diferencia entres este y el signo ‘>’  único es que los viejos contenidos (si los tuviese) del fichero fichero–salida no son eliminados. En su lugar, la salida que genera awk es añadida a dicho fichero.

print items ¦ comando

También es posible enviar la salida a través de un pipe en lugar de a un fichero. Este tipo de redireccionamiento abre un pipe a comando y escribe los valores de items a través de este pipe, a otro proceso creado para ejecutar comando.

El argumento de redirección comando es realmente una expresión awk. Su valor se convierte a cadena, cuyo contenido nos proporcina el comando de shell que debe ser ejecutado.

Por ejemplo, este produce dos ficheros, una lista sin ordenar de nombres BBS y una lista ordenada en orden alfabético inverso:

awk '{ print $1 > "names.unsorted"

       print $1 ¦ "sort –r > names.sorted" }' Lista–BBS

Aquí la lista desordenada se escribe con una redirección normal mientras que la lista ordenada es escrita mediante un pipe al comando sort de Unix.

Aquí tienes un ejemplo que usa el redireccionamiento para enviar un mensaje a una lista de correos ‘bug–system’. Esto podría ser útil cuando se encuentran problemas en un script awk que se ejecuta periódicamente para el mantenimiento del sistema.

print "Awk script failed:", $0 ¦ "mail bug–system"

print "at record number", FNR, "of", FILENAME  ¦ "mail bug–system"

close("mail bug–system")

Llamamos a la función close aquí porque es una buena idea cerrar la tubería o pipe tan pronto como toda la salida ha pasado a través de ella.

El redireccionamiento de la salida usando ‘>’, ‘>>’, o ‘¦’ le pide al sistema que abra un fichero o pipe solo si el fichero o comando particular que has especificado no ha sido ya escrito por tu programa.

Cerrando los Ficheros de Salida y Pipes

Cuando se abre un fichero o pipe, el nombre de fichero o comando asociado con él es recordado por awk y las siguientes escrituras al mismo fichero o comando son añadidas a las escrituras previas. El fichero o pipe permanece abierto hasta que finaliza el programa awk. Esto es normalmente conveniente.

Algunas veces existe una razón para cerrar un fichero de salida o un pipe antes de que finalice el programa awk. Para hacer esto, utilice la función close, tal y como sigue:

close(nombre–fichero)

o

close(comando)

El argumento nombre–fichero o comando puede ser cualquier expresión. Su valor debe concordar exactamente con la cadena usada para abrir el fichero o pipe cuando se empezó a usar – por ejemplo, si abres un pipe con esto:

print $1 ¦ "sort –r > names.sorted"

entonces debes cerrarlo con esto:

close("sort –r > names.sorted")

Aquí están algunas razones por las cuales podrías necesitar cerrar un fichero de salida:

         Para escribir un fichero y leer el mismo posteriormente en el mismo programa awk. Cierra el fichero cuando hayas acabado de escribir en él; entonces ya puedes empezarlo a leer con getline.

         Para escribir numerosos ficheros, sucesivamente, en el mismo programa awk. Si no cierras los ficheros, eventualmente excederás el límite del sistema en el número de ficheros abiertos por un proceso. Así que cierra cada uno cuando hayas acabado de escribirlo.

         Para hacer que un comando acabe. Cuando redireccionas la salida a través de un pipe, el comando que lee del pipe normalmente sigue intentando leer entrada mientras el pipe esté abierto. A menudo esto significa que el comando no puede realizar su trabajo hasta que el pipe es cerrado. Por ejemplo, si redireccionas la salida al programa mail, el mensaje no se enviará realmente hasta que el pipe se cierre.

         Para ejecutar el mismo programa una segunda vez, con los mismos argumentos. Esto no es la misma cosa que darle más entrada a la primera ejecución!

Por ejemplo, supón que haces un pipe de la salida al programa mail. Si sacas varias líneas redirigidas a este pipe sin cerrarlo, crear un mensaje de varias líneas. En constraste, si cierras el pipe después de cada línea de salida, entonces cada línea creara un correo distinto.

Streams de Entrada/Salida Estándard

La ejecución convencional de programa tiene tres streams de entrada y salida disponibles para lectura y escritura. Estos son conocidos como la entrada estándar, salida estándar y salida del error estándar. Estos streams son, por defecto, entrada y salida por terminal, pero son a menudo redireccionados con el shell, a través de los operadores ‘<’, ‘<<’, ‘>’, ‘>>’, ‘>&’ y ‘¦’. El error estándar se usa solamente para la escritura de mensajes de error; la razón por la cual tenemos dos streams separadas, salida estándar y error estándar, es para que puedan ser redireccionados independientemente. En otras implementaciones de awk, la única forma de escribir un mensaje de error al error estándar en un programa awk es la siguiente:

print "Serious error detected!\n" ¦ "cat 1>&2"

Esto trabaja abriendo un pipeline a un comando del shell el cual puede acceder a la stream del error estándar. Esto está lejos de ser elegante, y también es ineficiente, ya que requiere un proceso separado. Por lo que la gente que escribe programas awk se han negado a menudo a hacer esto. En su lugar, han enviado los mensajes de error al terminal, tal y como sigue:

NF != 4 {

   printf("line %d skipped: doesn't have 4 fields\n", FNR) > "/dev/tty"

}

Esto tiene el mismo efecto la mayoría de las veces, pero no siempre: aunque el stream del error estandárd es normalmente el terminal, puede ser redireccionado, y cuando eso ocurre, la escritura al terminal no es lo correcto. De hecho, si awk se ejecuta desde un trabajo en background, podría no tener un terminal para poner la salida de dicho error. Entonces, abrir el dispositivo ‘/dev/tty’ fallaría.

Gawk proporciona nombres de ficheros especiales para acceder a los tres streams estándar. Cuando redireccionas la entrada o la salida en gawk, si el nombre de fichero encaja con uno de estos nombres especiales, entonces gawk utiliza directamente el stream con el que se corresponde.

‘/dev/stdin’

La entrada estándar (descriptor de fichero 0).

‘/dev/stdout’

La salida estándar (descriptor de fichero 1).

‘/dev/stderr’

La salida del error estándar (descriptor de fichero 2).

‘/dev/fd/n

El fichero asociado con el descriptor n. Tal fichero debería haber sido abierto por el programa que inicie la ejecución awk (normalmente el shell). A menos que hagas algo para que no sea así, solamente los descriptores 0, 1 y 2 están disponibles.

Los nombres de ficheros ‘/dev/stdin’, ‘/dev/stdout’, y ‘/dev/stderr’ son alias para ‘/dev/fd/0’, ‘/dev/fd/1’, y ‘/dev/fd/2’, respectivamente, pero los primeros nombres son más indicativos.

La forma más correcta para escribir un mensaje de error en un programa awk es usar ‘/dev/stderr’, algo como esto:

NF != 4 {

  printf("line %d skipped: doesn't have 4 fields\n", FNR) > "/dev/stderr"

}

El reconocimiento de estos nombres de fichero especiales es deshabilitado si awk está en modo compatibilidad (Ver la sección 14. Invocación de awk).

   
Índice
Manual