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 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.
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.
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 }' ListaBBS
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.
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.
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.
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. |
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.
Aquí tienes como usar printf para presentar una tabla
alineada:
awk '{ printf "%10s %s\n", $1, $2 }' ListaBBS
imprime los nombres de los bulletin boards ($1) del fichero ListaBBS 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 5555553
alponet 5553412
barfly 5557685
bites 5551675
camelot 5550542
core 5552912
fooey 5551234
foot 5556699
macfoo 5556480
sdace 5553430
sabafoo 5552127
¿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 }' ListaBBS
¿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 }' ListaBBS
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 }' ListaBBS
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.
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 > ficherosalida
Este tipo de redirección
imprime los items en el fichero de salida ficherosalida. El nombre del
fichero ficherosalida 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 ficherosalida es eliminado antes de que se escriba
en él la primera salida. Las escrituras siguientes no borrarán el ficherosalida,
pero sí añadirán a él. Si ficherosalida no existe entonces será creado.
Por ejemplo, aquí
aparece como un programa awk puede escribir una lista de nombres BBS a un fichero
‘listanombres’ y una lista de números de teléfonos a un fichero ‘listateléfonos’.
Cada fichero de salida contiene un nombre o número por línea.
awk '{ print $2 > "listateléfonos"
print $1 > "listanombres" }' ListaBBS
print items >> ficherosalida
Este tipo de redirección imprime los items en el fichero
de salida ficherosalida. La diferencia entres este y el signo ‘>’
único es que los viejos contenidos (si los tuviese) del fichero ficherosalida
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" }' ListaBBS
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 ‘bugsystem’. 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 bugsystem"
print "at record number", FNR, "of", FILENAME ¦ "mail bugsystem"
close("mail bugsystem")
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.
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(nombrefichero)
o
close(comando)
El argumento nombrefichero 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.
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).
Manual |