===== GREP y SED ===== ==== Antecedentes: editor de text ED ==== **ed** es un editor de línea. Fue desarrollado en una época en la que las terminales de computadora (teclado y pantalla) no eran lo más corriente. Usualmente se utilizaba una teletipo (una suerte de máquina de escribir: teclado con impresión en papel fanfold),para comunicarse con la computadora y por lo tanto no era posible desplegar datos en una pantalla. Para ahorrar papel, el contenido de un archivo se ingresaba una línea por vez y se empleaban comandos sofisticados para realizar la edición. De esta aplicación surgen maneras muy inteligentes y eficientes de procesar textos. Muchas de las ideas incorporadas a **ed** para trabajar eficientemente con textos migrarían a programas como **vi**, **grep** y **sed**. Referencias: * https://sanctum.geek.nz/arabesque/actually-using-ed/ * https://www.gnu.org/software/ed/manual/ed_manual.html * http://linuxclues.blogspot.com/2012/09/ed-tutorial-line-editor-unix.html * https://wolfram.schneider.org/bsd/7thEdManVol2/edtut/edtut.pdf Tutorial muy básico de ed: Ed tiene 2 modos: comando e inserción En el modo comando, la sintaxis para ingresar comandos es: [ dirección[,dirección]]comando[parámetros] donde lo que aparece entre paréntesis rectos es opcional Los comandos suelen limitarse a una letra. Las direcciones corresponden a números de línea, los parámetros son datos adicionales que en ocasiones el comando requiere. **ed** siempre arranca en modo comando. Para pasar a modo insertar o agregar, deben usarse los comandos **i** o **a**. Si se usa el comando **i**, se inserta texto antes de la línea corriente. Si se usa el comando **a**, se agrega texto luego de la línea corriente. Para terminar de insertar o agregar texto, se debe insertar una **.** aislado en una línea. Eso hace que **ed** pase del modo inserción a modo comando nuevamente. Sólo es posible ingresar comandos en el modo comando. Mientras que se está en el modo inserción, todo el texto que se inserte pasa a formar parte del archivo que se esté editando. Mientras se está editando el archivo, el texto existe en un "buffer" (podemos pensarlo como un área de trabajo, en la memoria de la computadora). Para salvar ese texto usamos el comando **w**: * **w** salva el archivo a disco * **w nombre** salva el contenido del archivo a disco con el **nombre** que le pongamos Para salir de **ed** podemos usar el comando **q**. Si no queremos salvar el contenido del archivo, pero queremos salir de **ed**, podemos forzar la salida con **Q**. Para distinguir cuando estamos en modo ''comando'' o modo ''inserción'', podemos activar un ''prompt'', con el comando **P**. de esa manera aparece un ''*'' (asterisco), que marca que estamos en modo ''comando''. Direccionamiento: Cómo elegimos la línea corriente: * Si ingresamos un número, esa línea de texto pasa a ser la línea corriente. * Ingresando **,** o **p** o **.** se imprime en pantalla el contenido de la línea corriente. * Ingresando **n**, se imprime en pantalla el contenido de la línea corriente, acompañada del correspondiente número de línea. Por defecto, cualquier comando que se ejecute, tiene efecto únicamente sobre la línea corriente. Para que el comando actúe sobre varias líneas, hay que especificar un rango de direcciones: [dirección[,dirección]] Rangos de líneas y cambio de línea corriente: * **3** nos movemos a la tercer línea (pasa a ser la línea corriente) * **$** vale por la última línea: la última línea pasa a ser la línea corriente * **+2** nos movemos 2 líneas hacia adelante * **-4** nos movemos 4 líneas hacia atrás * **3,7** vale por el rango de líneas desde la tercera a la séptima * **.** vale por la línea corriente * **,** vale por todo el rango de líneas. Es equivalente a **1,$** * **.,$** vale por el rango que va de la línea corriente a la última * /regex/ nos movemos a la siguiente línea que contenga la expresión regular * ?regex? nos movemos a la línea anterior que contenga la expresión regular Comando habituales: * **p** imprime la línea corriente * **,p** imprime todas las líneas * **,n** idem, pero incluyendo el número de línea adelante * **3,7n** imprimir las líneas de la 3 a la 7 inclusive * **/regex/** hace corriente la primer línea que contenga la expresión regular * **g/regex/p** imprime las líneas que contengan la expresión regular Cambiando una línea: * **3c** pasa a modo inserción y sustituye la línea 3 por el texto ingresado. Se sale del modo inserción con **.** aislado Borrando una línea (o rango de líneas): * **3d** borra (delete) la línea 3 * **3,7d** borra las líneas 3 a 7 inclusive Moviendo líneas: * **3m1** mueve la línea 3 a la primer posición * **1m$** mueve la primer línea al último lugar Sustituyendo texto: * **s/regex/texto/** sustituye la primer ocurrencia de ''regex'' por el correspondiente ''texto'' en la línea corriente. Parámetros adicionales: * **s/regex/texto/3** sustituye la tercer ocurrencia de ''regex'' (si regex está al menos 3 veces presente en la línea * **s/regex/texto/g** sustituye todas las veces que aparezca ''regex'' * **3s/regex/texto/g** sustituye todas las veces que aparezca ''regex'' en la línea 3 * **3,7s/regex/texto/g** sustituye todas las veces que aparezca ''regex'' entre las líneas 3 y 7 * **,s/regex/texto/g** sustituye ''regex'' en todo el archivo ==== GREP ==== El comando **g/re/p** de **ed** es el origen del nombre del comando **grep**. Sirve para buscar dentro de uno o más archivos, líneas que contengan una determinada expresión regular. grep "regex" archivo grep -e "regex" archivo **grep** puede invocarse también como **egrep** **fgrep** o **rgrep**. * **egrep** es equivalente a **grep -E**: extended ''regex'' * **fgrep** es equivalente a **grep -F**: fixed string (no se interpreta como ''regex'') * **rgrep** es equivalente a **grep -r**: recursive Opciones habituales: * **-i**: ignore case * **-v**: invert match (muestra las líneas que NO contienen ''regex'' * **-c**: sólo muestra cuantas líneas contienen ''regex'' * **-o**: sólo imprime la parte de la línea que cumple con ''regex'' * **-q**: quiet, no muestra nada, exit code 0 si encontró algo (se puede usar en setencias if) * **-H**: imprime el nombre del archivo donde encontró ''regex'' * **-n**: imprime el número de línea donde encontró ''regex'' * **--color=auto**: resalta ''regex'' con color * **-A NUM**: imprime la línea que contiene ''regex'' y las ''NUM'' líneas siguientes (after) * **-B NUM**: idem pero las ''NUM'' anteriores (before) * **-r**: busca recursivamente en todos los archivos del path y sus subdirectorios * **-E**: habilita el uso de expresiones regulares extendidas (comparar **-F**, **-P** y **-G**) === Expresiones Regulares === La expresión regular más sencilla es un texto cualquiera. grep "texto" archivo grep -i "texto" archivo Si usamos **fgrep** o **grep -F**, entonces se puede emplear cualquier caracter en en el ''"texto"'' que se busca. De lo contrario, hay algunos caracteres que tienen significado especial (metacaracteres). Si se desea incluir un metacaracter en la búsqueda, hay que anular su significado, antecediéndolo con ''\''. Metacaracteres y su uso: 1) conjuntos de caracteres: * **[...]** vale por algunos de los caracteres representados por ''...'' grep if if.txt grep "[iI]f" if.txt grep -i if if.txt * **[^...]** niega los caracteres especificados entre paréntesis rectos (complemento) * **[.-.]** rangos de caracteres. Ejemplo: [a-d6-9] vale por caracteres entre la ''a'' y la ''d'' o números entre 6 y 9 * **[:alnum:]**, **[:alpha:]**, etc. (ver manual de grep) corresponden a conjuntos particulares de caracteres Anclas: comienzo o fin de una línea: * **^** y **$** corresponden al comienzo y fin de una línea grep -i if if.txt grep -i "^if" if.txt Repetición: metacaracteres que se aplican a una regex que le antecede * **?** regex aparece cero o a lo sumo una vez * ***** regex aparece cero o más veces * **+** regex aparece 1 o más veces * **{n}** regex aparece exactamente ''n'' veces * **{,m}** regex aparece a lo sumo ''m'' veces * **{n,m}** regex aparece entre ''n'' y ''m'' veces Concatenación: las regex se pueden concatenar y valen por la unión de ambas regex grep "[iI][fF]" if.txt Alternación: usando **|** es posible especificar regex alternativas (equivale al operador lógico OR). egrep "IF|life" if.txt egrep "I[fF]|life" if.txt Precedencia: repetición > concatenación > alternación. La precedencia se puede cambiar usando paréntesis ''( )'' para agrupar sub-expresiones regulares. Expresiones regulares básicas vs. extendidas: en modo básico (por defecto), los metacaracteres **?**, **+**, **{**, **|**, **(** y **)** no funcionan, deben usarse precedidos por **\**. Es preferible siempre usar la **grep -E** o **egrep** Ejemplo más complejo: extraer emails de un mailbox Si quiero extraer líneas con direcciones de email: grep -E "\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}\b" mbox Si sólo quiero las direcciones de email, uso la opción '-o': grep -E -o "\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}\b" mbox Ejemplo: extraer número IP de un mailbox grep -E -o "([0-9]{1,3}[\.]){3}[0-9]{1,3}" mbox Ejemplo de PES (Potential Energy Surface): cálculo de HF: hf.out Extraigo las distancias: grep "^ ! R1 " hf.out|grep "DE/DX"|cut -b 22-25 >nada1 Otra forma de hacer esto: grep '^ !.*DE/DX' hf.out|tr -s " " "\t"|cut -f4 Extraigo las energías: grep '^ SCF Done: ' hf.out|cut -b 26-35 >nada2 Junto ambas columnas: paste nada1 nada2 Finalmente procesamos los datos con **pyxplot** para generar la curva de PES ==== SED: Stream Editor ==== **sed** es un descendiente directo de **ed**, sólo que en lugar de trabajar interactivamente con el editor, **sed** ejecuta uno o más comandos de **ed** en un **stream** de bytes que recibe por ''stdin'' o desde un archivo, volcando a ''stdout'' el resultado. Básicamente todo lo que se puede hacer en **ed** se puede hacer con **sed**. Ejemplos: sed -nr '/if/p' if.txt sed -nr '/[iI][fF]/p' if.txt sed -nr '/^[iI][fF]/p' if.txt sed -rn 's/If/IF/' if.txt sed -rn 's/If/IF/p' if.txt sed -r 's/^I[fF]/SI/' if.txt sed -r 's/^ *$/ --------/' if.txt Volvemos al ejemplo del HF: en el ejemplo que vimos usamos **cut** para extraer las columnas 26 a 35 grep '^ SCF Done: ' hf.out | cut -b 26-35 pero si sustituimos los espacios en blanco por tabuladores, entonces podemos usar **cut** haciendo referencia al campo (-f): grep '^ SCF Done: ' hf.out | sed 's/^ //; s/ */\t/g' | cut -f5 Ojo con los metacaracteres: a veces debo usar regex extendido: grep '^ SCF Done: ' hf.out | sed -r 's/^ //; s/ +/\t/g' | cut -f5