Edición de archivos con awk#

awk es un comando para Unix creado para el procesamiento y reporte de archivos de texto que contengan varios campos de datos en una misma línea. Se considera que awk es una evolución de sed. Su uso básico es:

awk [condición] '{printf"formato", argumentos}' filename

Manual y ayuda en línea#

man awk

Algunos comandos importantes#

  • $0 -> Todos los campos

  • FS -> Separador de campos (TAB por defecto)

  • NF -> Número de campos en la linea actual

  • NR -> Número de lineas en el archivo a procesar

  • Length -> Longitud de la linea a procesar

  • && -> Operación lógica para la intersección

  • || -> Operación lógica para la unión

Impresión de líneas específicias#

[1]:
%%bash
# apt-get update
# apt-get install gawk
[2]:
%%bash
#
# se imprimen los primeros 30 números a un archivo
#
seq 30 > out.1
[3]:
%%bash
#
# imprime la línea 3
#
awk 'NR == 3 {print}' out.1
3
[4]:
%%bash
#
# imprime las líneas 3 a 6
#
awk '(NR >= 3) && (NR <= 6) {print $0}' out.1
3
4
5
6
[5]:
%%bash
#
# imprime las líneas 3 a 6 y 11 a 13
#
awk '((NR >= 3) && (NR <= 6) || ((NR >= 11) && (NR <= 13)))  {print $0}' out.1
3
4
5
6
11
12
13

Filtrado#

[6]:
%%bash
#
# Imprime las lineas que contengan un 1
#
awk '/1/ {print}' out.1
1
10
11
12
13
14
15
16
17
18
19
21

La cadena /1/ indica que la línea contenga un 1, los / son delimitadores y la cadena {print} al final indica que se imprima la línea.

[7]:
%%bash
#
# Imprime las líneas que tengaun un 1 al final
#
awk '/1$/ {print}' out.1
1
11
21
[8]:
%%bash
#
# Imprime las líneas que tengan un 1 al inicio
#
awk '/^1/ {print}' out.1
1
10
11
12
13
14
15
16
17
18
19

Sustitución#

En el archivo generado a continuación se desea substituir la X por x:

[9]:
%%bash
cat > out.1 <<EOF
FieldA, fieldD, gieldE, FieldG
   2, X, 2X, 2XG
   2, Y, 2Y, 2YG
   3, Y, 3Y, 3YG
   3, X, 3X, 3XG
   4, Z, 4Z, 3XG
EOF

awk permite realizar sustituciones mediante el comando gsub() compuesto por una expresión regular que busca el patrón y el elemento que reemplazará o modificará el elemento encontrado. La g indica que es global, en caso de que se requiera una sustitución local se debe utilizar la función sub().

En el siguiente código se sustituye únicamente la primera ocurrencia en cada línea del archivo:

[10]:
%%bash
#
# sustituye la primera ocurrencia de X por x en cada línea
#
awk '{sub(/X/, "x"); print}' out.1
FieldA, fieldD, gieldE, FieldG
   2, x, 2X, 2XG
   2, Y, 2Y, 2YG
   3, Y, 3Y, 3YG
   3, x, 3X, 3XG
   4, Z, 4Z, 3xG

Si se desean reemplazar todas las ocurrencias se usa gsub.

[11]:
%%bash
#
# Sustituye todas las ocurrencias de X por x
#
awk '{gsub(/X/, "x"); print}' out.1
FieldA, fieldD, gieldE, FieldG
   2, x, 2x, 2xG
   2, Y, 2Y, 2YG
   3, Y, 3Y, 3YG
   3, x, 3x, 3xG
   4, Z, 4Z, 3xG

Se puede utiliza el comando gsub para varias sustituciones separandolo por ;:

[12]:
%%bash
awk '{gsub(/X/, "x"); gsub(/Y/, "y"); print}' out.1
FieldA, fieldD, gieldE, FieldG
   2, x, 2x, 2xG
   2, y, 2y, 2yG
   3, y, 3y, 3yG
   3, x, 3x, 3xG
   4, Z, 4Z, 3xG

Extracción#

La función split permite separar strings e incluir sus elementos en un array, el cual se recorre de acuerdo con la posición. Sigue la siguiente estructura:

split($0,nombre_arreglo,"separador")

Sea el siguiente archivo:

[13]:
%%bash
cat > out.1 <<EOF
AAA-BBB-CCC
DDD-EEE-FFF
GGG-HHH-III
EOF

La segunda columna puede extraerse con:

[14]:
%%bash
#
# Parte la linea por los caracteres indicados y la asigna a una variable
#
awk '{split($0,nombre,"-")} {print nombre[2]}' out.1
BBB
EEE
HHH

A continuación se presenta un ejemplo más complejo de modificación de un patrón. Sea el siguiente archivo:

[15]:
%%bash
cat > out.1 <<EOF
Maria-1998:feb:2+M19
David-1972:nov:25+J45
Marco-2000:jun:4+V17
EOF

Se desea formatear la fecha de nacimiento completa de la persona y su edad, es decir, la primera línea:

Maria-1998:feb:2+M19

debe cambiarse por:

Maria 1998-02-02 M 19

La función gensub permite realizar busquedas a través de expresiones regulares y reemplazar dichos valores por elementos a elección de acuerdo con la frecuencia de coincidencia del patrón.

El primer paso consiste en reemplazar : por -.

[16]:
%%bash
cat out.1
Maria-1998:feb:2+M19
David-1972:nov:25+J45
Marco-2000:jun:4+V17
[17]:
%%bash
awk '{print gensub(/:([a-zA-Z]*)/,"-""\\1",1)}' out.1 > out.2
cat out.2
Maria-1998-feb:2+M19
David-1972-nov:25+J45
Marco-2000-jun:4+V17

La explicación del comando anterior es la siguiente:

Patrón entrada

  • El caracter / indica el inicio y el fin de la expresión regular.

  • Los caracteres ( y ) contienen la expresión, caracter o dígito que se desea guardar.

  • [a-z] indica una cadena.

  • El caracter * indica que existen cadenas luego de la expresión regular.

Patrón salida * \\\1 primer elemento de la busqueda que fue guardado.

Frecuencia * 1 se debe reemplazar cuando encuentre el patrón por primera vez.

[18]:
%%bash
awk '{print gensub(/:([0-9])/, "-\\1", 1)}' out.2 > out.3
cat out.3
Maria-1998-feb-2+M19
David-1972-nov-25+J45
Marco-2000-jun-4+V17
[19]:
%%bash
awk '{print gensub(/-([0-9])*/, "-0\\1", 3)}' out.3 > out.4
cat out.4
Maria-1998-feb-02+M19
David-1972-nov-05+J45
Marco-2000-jun-04+V17
[20]:
%%sh
awk '{print gensub(/+([A-Z])([0-9][0-9])/, " \\1 \\2", 1)}' out.4 > out.5
cat out.5
Maria-1998-feb-02 M 19
David-1972-nov-05 J 45
Marco-2000-jun-04 V 17
[21]:
%%bash
awk '{sub(/-/, " "); print}' out.5 > out.6
cat out.6
Maria 1998-feb-02 M 19
David 1972-nov-05 J 45
Marco 2000-jun-04 V 17
[22]:
%%bash
awk '{gsub(/feb/, "02"); gsub(/nov/, "11"); gsub(/jun/, "06"); print}' out.6
Maria 1998-02-02 M 19
David 1972-11-05 J 45
Marco 2000-06-04 V 17

Agregación de campos#

Sea el siguiente archivo:

[23]:
%%bash
cat > out.1 <<EOF
Date, Price, Quantity, CustomerID
2013-01-12, 25, 7, 1
2014-05-12, 41, 5, 12
2013-02-25, 44, 3, 2
2013-04-04, 90, 1, 5
2013-06-21, 16, 2, 19
2014-05-12, 63, 2, 15
2014-05-12, 10, 4, 7
2013-02-28, 78, 8, 9
2013-08-02, 51, 1, 14
EOF

Se desea agregar un nuevo campo llamado Quantity-CustomerID que contenga la cantidad de producto y el cliente. El siguiente comando une las columnas de interés:

[24]:
%%bash
awk '{print gensub(/, ([0-9][0-9]), ([0-9]), ([0-9])/, ", \\1, \\2-\\3", 1)}' out.1 > out.2
cat out.2
Date, Price, Quantity, CustomerID
2013-01-12, 25, 7-1
2014-05-12, 41, 5-12
2013-02-25, 44, 3-2
2013-04-04, 90, 1-5
2013-06-21, 16, 2-19
2014-05-12, 63, 2-15
2014-05-12, 10, 4-7
2013-02-28, 78, 8-9
2013-08-02, 51, 1-14

Se agrega el título Quantity-CustomerID:

[25]:
%%bash
awk '{print gensub(/([a-zA-Z]*), ([a-zA-Z]*), ([a-zA-Z]*), ([a-zA-Z]*)/, "\\1, \\2, \\3-\\4", 1)}' out.2
Date, Price, Quantity-CustomerID
2013-01-12, 25, 7-1
2014-05-12, 41, 5-12
2013-02-25, 44, 3-2
2013-04-04, 90, 1-5
2013-06-21, 16, 2-19
2014-05-12, 63, 2-15
2014-05-12, 10, 4-7
2013-02-28, 78, 8-9
2013-08-02, 51, 1-14

A través del comando BEGIN y END se pueden agregar valores al principio y al final de las columnas o de todo el archivo de texto.

El comando NR>1 indica que se debe tener en cuenta solo filas a partir de la posicion 1 y {print $0} que se deben tener en cuenta todas las columnas.

[26]:
%%bash
awk 'BEGIN{print "Date, Price, Quantity-CustomerID"}(NR>1){print $0}' out.2
Date, Price, Quantity-CustomerID
2013-01-12, 25, 7-1
2014-05-12, 41, 5-12
2013-02-25, 44, 3-2
2013-04-04, 90, 1-5
2013-06-21, 16, 2-19
2014-05-12, 63, 2-15
2014-05-12, 10, 4-7
2013-02-28, 78, 8-9
2013-08-02, 51, 1-14
[27]:
%%bash
awk '(NR>1){print $3} END{print "Hola"}' out.2
7-1
5-12
3-2
1-5
2-19
2-15
4-7
8-9
1-14
Hola

Se desea agregar un nuevo campo llamado Quantity*Price que contenga el total de la cuenta de cada compra. El comando {print $1 $2} concatena e imprime columnas iniciando el conteo en 1 de izquierda a derecha.

[28]:
%%bash
awk -F"," '{print $1","$2","$3", "$4", "$2*$4}' out.1 > out.2
cat out.2
Date, Price, Quantity,  CustomerID, 0
2013-01-12, 25, 7,  1, 25
2014-05-12, 41, 5,  12, 492
2013-02-25, 44, 3,  2, 88
2013-04-04, 90, 1,  5, 450
2013-06-21, 16, 2,  19, 304
2014-05-12, 63, 2,  15, 945
2014-05-12, 10, 4,  7, 70
2013-02-28, 78, 8,  9, 702
2013-08-02, 51, 1,  14, 714

Ahora, se debe agregar el título a la nueva columna:

[29]:
%%bash
awk '{print gensub(/, 0/, ", Total", 1)}' out.2
Date, Price, Quantity,  CustomerID, Total
2013-01-12, 25, 7,  1, 25
2014-05-12, 41, 5,  12, 492
2013-02-25, 44, 3,  2, 88
2013-04-04, 90, 1,  5, 450
2013-06-21, 16, 2,  19, 304
2014-05-12, 63, 2,  15, 945
2014-05-12, 10, 4,  7, 70
2013-02-28, 78, 8,  9, 702
2013-08-02, 51, 1,  14, 714

Se puede utilizar el comando la opción -F y OFS para cambiar el separador del archivo de texto:

[30]:
%%bash
awk -F"," 'BEGIN{OFS="|";}{print $1,$2,$3,$4}' out.1
Date| Price| Quantity| CustomerID
2013-01-12| 25| 7| 1
2014-05-12| 41| 5| 12
2013-02-25| 44| 3| 2
2013-04-04| 90| 1| 5
2013-06-21| 16| 2| 19
2014-05-12| 63| 2| 15
2014-05-12| 10| 4| 7
2013-02-28| 78| 8| 9
2013-08-02| 51| 1| 14

Resumen#

## imprime la línea 3
awk 'NR == 3 {print}' out.1

## imprime las líneas 3 a 6
awk '(NR >= 3) && (NR <= 6) {print $0}' out.1

## imprime las líneas 3 a 6 y 11 a 13
awk '((NR >= 3) && (NR <= 6) || ((NR >= 11) && (NR <= 13)))  {print $0}' out.1

## Imprime las lineas que contengan un 1
awk '/1/ {print}' out.1

## Imprime las líneas que tengaun un 1 al final
awk '/1$/ {print}' out.1

## Imprime las líneas que tengan un 1 al inicio
awk '/^1/ {print}' out.1

## sustituye la primera ocurrencia de X por x en cada línea
awk '{sub(/X/, "x"); print}' out.1

## Sustituye todas las ocurrencias de X por x
awk '{gsub(/X/, "x"); print}' out.1

## Parte la linea por los caracteres indicados y la asigna a una variable
awk '{split($0,nombre,"-")} {print nombre[2]}' out.1

Borrado de los archivos temporales creados

[31]:
!rm out.*