TUTORIAL DE SED --------------- Índice propuesto: 1. ¿Ha oído hablar de las expresiones regulares? 2. Ejecución y funcionamiento de sed 2.1. Qué hace sed 2.2. Sintaxis básica 2.3. Pattern space y hold space 2.4. Ciclo normal de ejecución 2.5. Comandos 2.6. Cómo alterar el ciclo de ejecución 3. Enlaces recomendados 3.1. Expresiones regulares 3.2. sed 4. Guiones comentados 4.1. Guiones en una línea 4.2. Extracción de líneas 4.3. Formateo 4.4. Matemáticas 4.4. Emulación de comandos de unix tac wc -l wc -c cat -n/cat -b unix2dos dos2unix Tareas pendientes: a) Escribir la sección 2.5 1. ¿Ha oído hablar de las expresiones regulares? ¿Sabe usted qué significa /^\(\w\+\) \1$/? Si la respuesta es no, entonces le aconsejo que, antes de seguir leyendo, se instruya en el uso de las expresiones regulares. Yo no las trataré aquí, pero no se apure: hay muchísima información disponible en castellano. Consulte la sección de enlaces, si quiere ahorrarse el trabajo de buscar en google: creo que es suficiente con lo que viene explicado en ellos. 2. Ejecución y funcionamiento de sed 2.1. Qué hace sed sed es un editor de flujo que toma dos datos de entrada: un flujo de entrada a manipular (p.e. un fichero de texto) y el conjunto de órdenes (guión) que describen la manipulación. Como flujo de salida devuelve el flujo de entrada modificado según el guión de órdenes: -- flujo de entrada --, |--> sed ---> flujo de salida modificado ------- guión --------' Si no se especifica el fichero de entrada, sed entiende que se proporcionará a través de la entrada estándar (teclado). Del mismo modo, si no se direcciona la salida, sed enviará el resultado a la salida estándar (pantalla). Por ejemplo en: $ sed 's/A-Z/a-z/' texto.txt tenemos que flujo de entrada: fichero texto.txt guión de órdenes: 's/A-Z/a-z/', una sola orden en este caso Traducida a román paladino, la línea quiere decir: toma el fichero texto.txt (flujo de entrada), conviérteme sus letras mayúsculas en minúsculas (guión de órdenes) mándame (imprímeme) el resultado a la pantalla. 2.2. Sintáxis básica Existen dos formas de indicar a sed cuál es el guión de órdenes: a) En línea: sed [-e] 'órdenes-del-guión' fichero.txt Indicar el parámetro '-e' es optativo. Al menos en la versión GNU de sed, que es la que yo uso. 'órdenes-del-guión' es una sucesión de órdenes de sed separadas por punto y coma (;). Por ejemplo, sed 'N;P;d' fichero.txt es un guión compuesto por tres órdenes: N, que añade al final del escritorio la siguiente línea de fichero.txt; P, que imprime la primera línea del escritorio; y d, que lo borra. El resultado de estas operaciones es un flujo de salida que devuelve las líneas impares del fichero. No se preocupe, si no entiende por qué es así: aún no tiene suficientes elementos de juicio. b) Mediante un fichero: sed -f guion.sed fichero.txt En este caso las órdenes de sed están incluidas en un fichero denominado según el ejemplo "guion.sed". Es equivalente a usar la forma anterior, aunque esta suele usarse cuando el guión es más largo y su lectura en una línea es complicada de entender o escribir. Si se usa el mismo ejemplo anterior, hacer un "cat" de guion.sed devuelve: ### Principio del script ### N # Añade otra línea al escritorio P # Imprime la primera línea del escritorio d # Borra escritorio e inicia un nuevo ciclo ### Fin del script ### El uso de la almohadilla (#) para indicar el inicio y el fin del guión y los comentarios no ha sido arbitrario: es el carácter para señalar los comentarios en sed. Si, por alguna razón, se desean escribir en una misma línea dos órdenes distintas bastará separarlas como en el caso anterior mediante punto y coma (;). Evidentemente, sed admite otros parámetros aparte de -e y -f. Si está interesado en ellos consulte la página del manual o el archivo info. Aparte de estas dos formas básicas, la shell de bash (lo siento, es la única que conozco) nos permite hacer una variación sobre la forma a) (guión en línea) y una variación sobre la forma b) (guión en fichero). Tenga claro que estas posibilidades no son exclusivas de sed, sino que son consecuencia de las posibilidades que nos brinda bash. Por tanto, si hablásemos de awk o cualquier otro comando de unix, podríamos también hablar de ellas. a bis) guión de órdenes en documento interno: sed -f - << "FIN" fichero.txt N P D FIN Es una forma de lograr escribir en línea el guión con la claridad de separar en distintas líneas las órdenes. Advierta que he incluido el primer FIN entre comillas para evitar que bash realice alguna expansión (a "*","$", etc...) en el guión. Es un método muy útil cuando se está escribiendo un guión de bash que incluye una llamada a sed. Si el guión de sed incluido es pequeño, bastará la forma a); pero si es largo y complicado de entender y queremos tener todo reunido dentro del guión de bash, se puede echar mano de este método. Si tiene alguna duda, consulte la sección Documentos Internos (Here Documents) de la página del manual de bash. b bis) Invocación automática de sed gracias a la línea de "shebang": Consiste simplemente en indicarle a bash dentro del propio fichero de guión cuál es el programa que debe invocar. En nuestro caso: #!/bin/sed -f P N d Una vez hecho esto, le damos los permisos de ejecución adecuados al fichero y podremos ejecutarlo directamente: $ ./guion.sed 2.3. Pattern space y hold space sed posee dos buffer en los que almacena información: el pattern space y el hold space. Yo a partir de ahora los llamaré (porque me da la gana de hacerlo así) el escritorio y la gaveta: no es una traducción literal del inglés; ni siquiera una traducción que haya leído en algún sitio. Simplemente me parece que representa muy bien la función de ambos. Si no te gusta, ya sabes: sed 's/escritorio/pattern space/g;s/gaveta/hold space/g' tutorial.txt Bueno, bueno... en realidad no lo sabes aún, pero espero que acabes sabiéndolo. ;-) Concretando, como sus nombres indican, el escritorio es el espacio de trabajo al que se trae el flujo de entrada para manipularlo (borrar, modificar, imprimir, etc...) y la gaveta es un espacio auxiliar en que se pueden meter datos que se prevé usar más adelante sacándolos al escritorio. Esto es importante tenerlo presente, puesto que en sed el trabajo en el escritorio es inmediato, es decir, del flujo de entrada solamente se puede manipular aquello que tenemos en ese preciso momento en él y no lo que ya pasó por él o lo que aún no ha pasado. 2.4. Ciclo normal de manipulación sed divide el flujo de datos de entrada en líneas y a cada una de ellas le aplica por separado el guión de órdenes. Por ejemplo, si tenemos un flujo constituido por tres líneas Esta es la primera Esta es la segunda Y esta la última sed lo tratará en tres ciclos e irá consecutivamente aplicando el guión a cada una de ellas. Por supuesto, eso significa que en el primer ciclo, tendremos en el escritorio la línea "Esta es la primera", en el segundo "Esta es la segunda" y en el último "Y esta la última". En principio (ya veremos que existen mecanismos para complicar las cosas), si estamos en el segundo ciclo, (es decir, con la línea "Esta es la segunda" en el escritorio) no podremos volver atrás a hacer algo que dejamos de hacer a la primera línea "Esta es la primera". Por el mismo principio, no podemos apoyarnos en algo que aún este por venir; por ejemplo, nos es imposible saber que estamos en la penúltima línea del fichero, porque sed aún no sabe las líneas que quedan por llegar. En general, un ciclo se compone de: 1) Comienzo, en que sed lee la siguiente línea del flujo de datos y la trae hasta el escritorio para que podamos manipularla. 2) Desarrollo, en que se aplica el guión al contenido del escritorio. 3) Final, en que se manda al flujo de salida lo que quede en el escritorio, se "tira a la basura" su contenido y se vuelve a comenzar un nuevo ciclo en caso de que queden más líneas por leer. Por supuesto, podemos actuar sobre este ciclo, leyendo líneas antes de comenzar un nuevo ciclo o ingeniándoselas para no tirar a la basura lo que tenemos en nuestro escritorio, etc..., pero antes de hablar de ello es mejor dar un repaso a los comandos. 2.5. Comandos 2.5.1 Lista de comandos [completar] Esto va a ser una traducción-resumen de lo referente a los comandos y su sintaxis que se puede leer en las páginas info y man). Hasta que tenga tiempo de escribirlo: man sed info sed 2.5.2 Ejecución condicional de comandos 2.5.2.1 Ejecución en una única línea Basta con anteponer el número de línea a la orden. Por ejemplo: sed '2d' fichero.txt Aplica la orden d únicamente a la segunda línea. El resultado es un flujo de salida con todo el fichero.txt excepto la segunda línea. La última línea del fichero se representa con un dólar ($). 2.5.2.2 Ejecución en una línea que contiene un patrón Se antepone la expresión regular a la orden. Por ejemplo sed '/^ /d' fichero.txt no manda al flujo de salida las líneas que empiezan por un espacio. 2.5.2.3 Ejecución en un intervalo Se separa el inicio y el fin del intervalo con una coma y a continuación se incluye la orden. Tanto el inicio como el fin pueden ser un número de línea o una expresión regular: 1,5d 1,/^$/b /^ /,40d /^A/,/\.$/p 2.5.2.4 Grupos de órdenes Se incluyen entre paréntesis ({}): sed '1,5{H;d};$G' fichero.txt Este guión aplica las órdenes H y d a las cinco primeras líneas y la orden G a la última. 2.5.2.5 No ejecución Esto es, ejecuta la orden excepto para tal línea o tal intervalo: se interpone un cierre de exclamación (!) entre la línea/intervalo y la orden. Ejemplos: 2!d ----> Borra la línea excepto si es la segunda /^ /!d ----> Borra la línea excepto si empieza con espacio 1,5!d ----> Borra la línea excepto si está en el intervalo 1-5 1,5!{H;d} ----> Aplica el grupo de órdenes excepto a las líneas 1-5 2.6. Cómo alterar el ciclo de ejecución Ya se ha explicado cuál es el ciclo de ejecución de sed: traer al escritorio una línea, cacharrear con ella, imprimirla, tirarla y volver a hacer lo mismo con la siguiente hasta que no queden más líneas por leer. Sin embargo, este comportamiento normal puede alterarse mediante: 1) Opción -n: simplemente no imprime el contenido del escritorio al final del ciclo, es decir: $ sed '' fichero.txt imprime todas las líneas del fichero tal cual (ya que no indicamos ninguna orden a sed), pero $ sed -n '' fichero.txt no imprime nada. Digamos que esta opción nos vuelve mudito a nuestro sed. Si no le indicamos que imprima expresamente algo (p o P), no lo hará. 2) Etiquetas, que en combinación con las órdenes b,t y T, impiden que el ciclo acabe y empiece el siguiente. Si eso ocurre, no sucederá nada de lo asociado al final y al comienzo de un ciclo (impresión, borrado y lectura de nueva línea). Por ejemplo: $ sed ':loop;b loop' fichero.txt Esta ejecución empezará el primer ciclo (o sea, leerá la primera línea de fichero.txt y la traerá al escritorio). A continuación, se encontrará con un salto incondicional a la etiqueta ":loop", así que saltará hacia adelante; volverá a encontrarse con el salto y volverá hacia atrás y así ad infinitum... Consecuencia: acabamos de crear un lindo bucle infinito que nos impedirá leer alguna vez la segunda línea. 3) Órden d: Esta orden borra todo el escritorio y comienza un nuevo ciclo de forma normal aunque aún queden órdenes por ejecutarse. 5) Órden D: Borra sólo la primera línea del escritorio. Si el escritorio se queda vacío empieza un ciclo normalmente (lee la siguiente línea del fichero). Si no está vacío, empieza un ciclo sin leer la siguiente línea. Por ejemplo: $ sed 'G;D' fichero.txt Este programa se ejecutará eternamente sin que jamás se llegue a leer la segunda línea del fichero. La razón es que G añade al final del escritorio lo que hay en la gaveta (si no se guardó previamente nada, una línea en blanco). Por ello, al ejecutar "D", el escritorio siempre contendrá dos líneas, la primera de las cuales será borrada. Como seguirá habiendo una, no se leerá la siguiente línea de fichero.txt, sino que se seguirá trabajando con la línea que queda. Pero G volverá a añadir una segunda línea, lo que impedirá a "D" lograr vaciar completamente el escritorio. Consecuencia: otro lindo bucle infinito. 6) Órdenes n/N: nos permiten leer la siguiente línea sin esperar al fin del ciclo. La primera reemplaza el contenido del escritorio, la segunda añade la nueva línea al final. 3. Enlaces recomendados 3.1. Expresiones regulares Artículo de LinuxFocus que sirve a modo de introducción: http://www.linuxfocus.org/Castellano/July1998/article53.html Tutorial bastante extenso en bulma: http://bulma.net/impresion.phtml?nIdNoticia=770 3.2. sed La página info de sed (la del manual es bastante escueta): info sed sed trabajando en una sola línea: colección de manipulaciones que sed puede hacer con guiones de una sola línea: http://www.student.northpark.edu/pemente/sed/sed1line.txt Tutorial de sed: sencillamente delicioso. Es el documento con el que he comprendido exactamente cómo funciona sed. Contiene una pequeña introducción a sed con la filosofía de su funcionamiento y la explicación de sus comandos. Además incluye multitud de guiones, más o menos extensos, profusamente comentados. Su didáctica es la que pretende imitar este tutorial, que toma de él muchos de los ejemplos. 4. Guiones comentados 4.1. Guiones en una línea * Mostrar la línea octava del fichero Borramos cualquier línea que no sea la octava. Además tras imprimirla, salimos de sed, puesto que el resto del fichero no nos interesa en absoluto. sed '8!d;q' fichero.txt Obsérvese que no hace falta especificar a qué línea queremos aplicar q, porque "d" (que se ejecuta para las siete primeras líneas) aborta el resto del guión e inicia un nuevo ciclo. * Borrar desde la línea 3 a la 6 del fichero: Borramos el intervalo de líneas entre la 3 y la 6 sed '3,6d' fichero.txt * Mostrar entre la línea 3 y la 6 del fichero Borramos cualquier línea que no se encuentre en ese intervalo. Tras imprimir la sexta salimos, puesto que el resto del fichero no nos interesa en absoluto. sed '3,6!d;6q' fichero.txt En este caso, sí es necesario indicar la línea en que se quiere ejecutar "q", puesto que las líneas 3,4 y 5 no ejecutan d y, por tanto, continúan la ejecución normal del bucle. * Añadir una línea en blanco después de cada párrafo. Vamos a considerar que un fin de párrafo se produce cuando hay una línea que acaba en punto (.). El método es muy simple: cada vez que encontramos una línea que acaba en punto añadimos al escritorio una línea en blanco. Para esto, simplemente usamos el comando "G", puesto que si no se ha almacenado nada, la gaveta contiene una línea vacía. sed '$q;/\.$/G' fichero.txt * Imprimir las líneas impares de un fichero. En este caso el método consiste en leer traer líneas en cada ciclo, pero sólo imprimir la primera. sed -n '$!N;P' fichero.txt Como se ha indicado la opción '-n', no hace falta borrar el escritorio antes de que acabe el ciclo. Otra alternativa es: sed '$!N;s/\n.*$//' fichero.txt En este caso, la manera de evitar que se devuelva la línea par es haciéndola desaparecer mediante una sustitución. * Imprimir las líneas pares de un fichero. El método equivalente al primero que se sugirió para el ejercicio anterior puede ser: sed -n '1!{$!N;P}' fichero.txt que es exactamente lo mismo, excepto por el hecho de que en la primera línea evitamos juntar con la siguiente. Como consecuencia, en el resto de ciclos las parejas de líneas serán 2-3, 4-5, 6-7, etc... Como de cada pareja imprimimos la primera, el resultado es que mostramos las líneas pares. Y el equivalente al segundo: sed '$!N;s/[^\n]*\n\?/' fichero.txt 4.2. Extracción de líneas * Mostrar las seis últimas líneas de un fichero: Por su forma de trabajar, sed no puede hacer pronósticos sobre cuando acabará el fichero. Por ello, es imposible usar expresiones del tipo $-6 y la única forma de resolver el problema planteado es mantener en el escritorio las seis últimas líneas leídas del fichero y, cuando lleguemos a la última, imprimir lo que haya en él. Vamos a plantear cuatro posibles soluciones. Por supuesto, todas se basan en la idea que se ha expresado arriba. La utilidad de revisarlas todas es que nos permite razonar sobre la forma en que actúa sed. Solución 1: : loop #1 1,5 { #2 N #3 b loop #4 } #5 $! { #6 N #7 D #8 } #9 Las órdenes #1-#5 crean un bucle que nos permite almacenar las seis primeras líneas del fichero en el escritorio. A partir de la séptima línea, se ejecuta el bloque #6-#9 que va eliminando la primera de las líneas almacenadas en el escritorio (la más antigua leída) y añadiendo al final la última leída. Como consecuencia, cuando leamos la última, el escritorio contendrá las seis últimas y no habrá que ejecutar ninguna orden, sino procurar que el ciclo acabe normalmente (impresión del escritorio). Solución 2: Si nos fijamos en el guión anterior, en todos los ciclos se ejecuta una N, así que vamos a dar otra solución en que la orden N sea común a los dos bloques: :loop #1 N #2 2,5b loop #3 $!D #4 Sea cual sea la línea leída, añadimos al escritorio la siguiente (#2). Si hemos leído alguna de las cinco primeras líneas, volvemos a añadir sin borrar nada (#3). Como consecuencia, la última vez que se ejecute #3, acabaremos con las seis primeras líneas del fichero almacenadas en el escritorio. A partir de ese momento, #4 borra la primera línea e inicia un nuevo ciclo con cinco líneas en el escritorio. Cuando nos encontremos en la lectura de la penúltima línea, N añadirá la última. Sin embargo, en ella #4 no se ejecutará. Por tanto, acabará el último ciclo de forma normal, esto es, imprimiendo las seis últimas líneas del fichero. Solución 3: :loop #1 $q #2 N #3 2,6b loop #4 D #5 Muy parecida a la anterior. La única diferencia es que en este caso la última vez que se ejecute N (penúltima línea), también se ejecuta D (última línea). Por ello, no debemos almacenar seis líneas consecutivas, sino siete; que serán reducidas a seis cada vez que pasemos por D. Quizás es aconsejable explicar el final del proceso: en el penúltimo ciclo nos encontraremos en la penúltima línea, #3 añadirá la última línea y #5 borrará la primera de las líneas almacenadas (es decir, la séptima del fichero empezando a contar por el final). El último ciclo, simplemente, ejecuta #2, que imprime el escritorio y sale de sed. Solución 4: En ella en vez de expresar el primer grupo de líneas, expresamos el segundo. Nos vamos a basar en la solución 3: :loop #1 $q #2 N #3 7,$D #4 b loop #5 No es necesario hacer ningún comentario, ¿no? * Borrar las últimas seis líneas Como sed no sabe lo que aún queda por leer del fichero, la única forma de hacer esto es imprimir con seis líneas de retraso: cuando se va por la línea 7, imprimir la 1 y, por tanto, cuando se va por la última línea, imprimir la séptima por la cola y salir. Así que el problema es muy semejante al anterior: lograr reunir siete líneas en el escritorio y, antes de borrar la primera de ellas, imprimirla. En vez de un $q, se hace un $Q para desechar lo que queda en el pattern space (las últimas seis líneas). Tomemos la última de las soluciones del ejemplo anterior: :loop #1 $Q #2 N #3 7,$ { #4 P #5 D #6 } #7 b loop #8 Por supuesto, se puede tomar cualquiera. Tomar la penúltima nos ahorra los paréntesis: :loop #1 $Q #2 N #3 2,6b loop #4 P #5 D #6 * Devolver toda línea que contengan /patrón/ y la línea que inmediatamente preceda a cada una: La forma de razonar es idéntica a la de otros guiones: como sed no puede predecir qué contendrá la siguiente línea, es necesario tener dos líneas en el escritorio. /patrón/b #1 $!N #2 /\n.*patrón/!D #3 En caso de que leamos una línea que contenga "patrón", simplemente la imprimimos y pasamos a la siguiente (#1). Si no lo contiene, será necesario añadir la siguiente línea (#2) y comprobar (#3) si está contiene el "patrón". Si no es así, borramos la primera línea, conservamos la segunda (#3) y comenzamos ciclo con ella; si sí, acabamos ciclo normalmente para imprimir las dos líneas. * Devolver toda línea que contenga /patrón/, las inmediatamente anterior y posterior El algoritmo se basa en acumular líneas en el escritorio (siempre que se haya encontrado una línea que contenga /patrón/) hasta que la última no lo contenga: : loop #1 $!N #2 /patrón/!D #3 /patrón[^\n]*$/!b #4 $!b loop #5 #2 junta la línea actual con la siguiente. Si en el escritorio no se encuentra /patrón/, #3 borra la primera línea y regresa a #2 para añadir una nueva. En caso de que sí contenga /patrón/, #4 comprueba que la última línea no lo contiene y en tal caso acaba el ciclo normalmente (impresión y borrado del escritorio). En caso contrario, es necesario seguir añadiendo líneas al escritorio (#5), a menos que se haya llegado al final del fichero. * Borrar todo el contenido comprendido entre /inicio/ y /fin/ Por supuesto, no se supone que ambos patrones se encuentran en una misma línea, lo que convertiría el problema en trivial. En este caso, la forma de actuar es almacenar líneas en el escritorio a partir del momento en que hemos encontrado /inicio/ hasta que hallemos /fin/. /inicio/!b #1 : loop #2 $!N #3 s/inicio.*fin// #4 $!T loop #5 #1 acaba el ciclo normalmente si el escritorio no contiene /inicio/. En caso contrario se entra en un bucle (#2-#5) por el que se van sucesivamente añadiendo líneas al escritorio (#3). El bucle acaba, bien porque se ha llegado al final del fichero, bien porque se ha logrado (#5) borrar el contenido entre /inicio/ y /fin/ (#4). En ambos casos se acaba normalmente el ciclo. ¡Ojo! T es una extensión GNU de sed, así que si se usa otro sed es posible que no funcione. No obstante, la misma idea puede plasmarse sin usar T: /inicio/!b #1 : loop #2 $!N #3 s/inicio.*fin// #4 t #5 $!b loop #6 Es decir, si la sustitución tiene éxito, acabamos ciclo (#5) saltando #6; si no, regresamos a #3. * Imprimir la última línea de un grupo de líneas consecutivas que contienen todas un mismo campo inicial Basta con llevar siempre dos líneas en el escritorio y, en caso, de que los campos iniciales de ambas no coincidan, imprimir la primera. $b #1 N #2 /^\([^ ]* \).*\n\1/!P #3 D #4 #1 acaba ciclo en caso de que estemos en la última línea. En caso contrario, #2 añade la siguiente línea, #3 imprime la primera línea en caso de que los campos iniciales sean distintos y #4 la borra y comienza ciclo con la segunda línea. 4.3. Formateo * Extraer la cabecera de un correo, haciendo que cada campo (From, To, etc...) ocupe una sola línea A veces es necesario manipular cabeceras de correo. El problema es que es muy común que las cabeceras ocupen varias líneas; de modo que, si por ejemplo se quieren extraer todos los destinatarios de un mensaje, no basta con manipular la línea que contiene en patrón /^To: /. Por supuesto, se pueden usar utilidades como formail para remediar este problema, pero vamos a intentar resolverlo todo con sed. La estrategia es añadir líneas al escritorio hasta que nos topemos con un nuevo campo. En ese momento, se imprime el que se ha completado y se comienza a completar el nuevo. /^$/Q #1 : loop #2 N #3 /\n[ \t][^\n]*$/b loop #4 s/\n[ \t]\+/ /g #5 P #6 D #7 El grupo #2-#4 es un bucle que añade líneas al escritorio hasta que sed encuentre una que no empiece por caracteres blancos, señal de que empieza otro campo o se ha llegado al final de la cabecera. Al salir del bucle #5 elimina todos los cambios de línea, excepto el último; #6 imprime la primera línea (el campo completado) y #7 la borra conservando la segunda e iniciando un nuevo ciclo. Si esta segunda línea es una línea en blanco (fin de la cabecera), acabamos el proceso (#1). Obsérvese que si lo que se quiere es extraer un campo en concreto, basta con condicionar la orden P. Por ejemplo, /^\(From\|Cc\): /P imprimiría los campos de remitente y copia de carbón. * Separar con una línea en blanco grupos de líneas consecutivos que contienen todas un mismo campo inicial $q #1 N #2 P #3 /^\([^ ]* \).*\n\1/!i\ #4 D #5 #2 añade la línea siguiente, #3 imprime la primera línea del escritorio y, si la segunda línea empieza con distinto campo, #4 incluye en el flujo de salida una línea en blanco. En cualquier caso, #5 borra la primera línea y comienza un nuevo ciclo con la segunda. * Fusionar líneas hasta que haya una que acabe en punto (.): : loop #1 /\.$/b #2 N #3 y/\ / / #4 $!b loop #5 Si el contenido del escritorio acaba en punto, se acaba el ciclo (#2). Si no es así, #3 añade la siguiente línea y #4 elimina el cambio de línea. A menos que hayamos llegado al final, #5 vuelve a #2 sin alterar el escritorio. * Separar párrafos y sangrar (se supone que un párrafo acaba cuando nos encontramos una línea que acaba en punto) Como se debe actuar sobre la línea siguiente a la que contiene un punto, es necesario tener dos líneas en el escritorio: /\.$/!b #1 $q #2 p #3 i\ #4 N #5 s/[^\n]*$/\t&/ #6 D #7 #1 imprime la línea sin más en caso de que no se trate un final de párrafo. En caso contrario, #2 comprueba si es la última, en cuyo caso la acción se reduce también a imprimir. A partir de #2, se ejecuta el algoritmo para incluir una línea en blanco: #3 imprime, #4 incluye la línea en blanco, #5 añade la siguiente línea y #6 le realiza el sangrado. Como remate, #7 borra la primera línea (que ya se imprimió) y se inicia un nuevo ciclo con la segunda. 4.4. Matemáticas * Dado un fichero en que cada línea es un número, sumar una unidad a cada uno de ellos. sed no tiene ningún comando para realizar sumas, de modo que para poder realizar esto, se tiene que estudiar cómo cambia un número al que se le suma una unidad y realizar las sustituciones pertinentes. Si le sumamos una unidad: a) Un número que esté constituido únicamente por nueves, añade una cifra más a su izquierda: 999 (3 cifras) ---> 1000 (4 cifras). b) Un número puede considerarse constituido por una parte variante y una parte invariante. La parte variante está constituida por todos los nueves que hay más a su derecha y la cifra inmediatamente adyacente a la izquierda de este grupo de nueves. Por ejemplo, en el caso 1269 ---> 1270. La parte variante es 69 y la invariante 12. En el caso particular de que no haya nueves, podemos considerar que el grupo de nueves está constituido por 0 nueves, de modo que la parte variante es únicamente el número que hay más a la derecha: 1234 ---> 1235. Parte variante, 5; parte invariante, 123. Así pues, si usamos como separador "x" de ambas parte, algunos ejemplos son: 1999 ----> x1999 12 ----> 1x2 1289 ----> 12x89 999 ----> 0999 (aplicando a) primero) ----> x0999 Expresado en la sintaxis de sed la parte variante es /.9*$/. c) Las cifras que varían lo hacen de modo que 0->1, 1->2, ..., 0->9. Expresado en la sintaxis de sed: y/0123456789/1234567890/. Con todo esto ya se puede construir el algoritmo: s/^9*$/0&/ #1 s/.9*$/x&/ #2 h #3 s/*x// #4 y/0123456789/1234567890/ #5 x #6 s/x*// #7 G #8 s/\n// #9 #1 añade un cero delante los números constituidos únicamente por nueves. #2 separa la parte variante de la invariante mediante una "x". Ahora es necesario cambiar la parte variante. Sin embargo, como el comando y es indiscriminado y no se le puede pedir que altere sólo a partir de la "x", no nos queda más remedio que: meter una copia en la gaveta (#3), eliminar la parte invariante (#4), hacer la variación (#5), intercambiar los contenidos del escritorio y la gaveta (#6), eliminar la parte variante (#7), añadir al escritorio la gaveta (#8) y, dado que la primera línea contiene la parte invariante y la segunda la variante ya variada, eliminar el retorno de carro (#9). * Trasponer una matriz que viene dada en un fichero de la forma: 11 12 13 21 22 23 31 32 33 Algoritmo: : inicio #1 $! { #2 N #3 b inicio #4 } #5 s/^/\n/ #6 : fila #7 s/$/\a/ #8 : columna #9 s/\n\(\w\+\)[ \t]*\(.*\)$/|\2\1 / #10 t columna #11 y/|/\ / #12 s/^\n*\a// #13 t fin #14 b fila #15 : fin #16 s/ *\a */\n/g #17 El bucle #1-#6 trae al escritorio todas las líneas del fichero. Una vez que se tiene toda la matriz, #8 añade al final el carácter "\a" que va a servir para separar la siguiente fila de la matriz traspuesta. El bucle #9-#10 coloca la columna n-ésima de elementos tras el último "\a" y separados por espacios. A la vez va sustituyendo los cambios de línea "\n" por el carácter "|". Cuando ya no queden más "\n" significará que la columna se ha completado y saldremos del bucle. #12 restituye los "\n" y #13 intenta sustituir una cadena de "\n" desde el inicio del escritorio al primer "\a". Si no lo consigue es señal de que quedan más columnas en la matriz y se ha de comenzar de nuevo (#16). En caso contrario, #15 nos lleva al final #17, en que se sustituyen los "\a" por cambios de línea El algoritmo tiene una limitación: no se puede usar "|" en los elementos de la matriz, ya que es usado como carácter de control. Se usa "|" y no cualquier otro carácter de control (por ejemplo, "\r") porque la orden "y" de sed no permite usarlos :(. Se puede estar tentado de usar "s" en vez de "y" en #12 para evitar este problema. Sin embargo, esta "s", alteraría el funcionamiento de la condición #14. Lo que sí puede hacerse es desplazar la sustitución hasta después de #14. Por ejemplo: : inicio #1 $! { #2 N #3 b inicio #4 } #5 s/^/\n/ #6 : fila #7 s/$/\a/ #8 : columna #9 s/\n\(\w\+\)[ \t]*\(.*\)$/\r\2\1 / #10 t columna #11 s/^\r*\a// #12 t fin #13 <--------------------------. s/\r/\n/g #14 <--- "s" pero después de --' b fila #15 : fin #16 s/ *\a */\n/g #17 * Hacer las sumas en ficheros con formato: num1+num2+num3 num4+num5 num6+num7+num8+num9 . . . Pues sí, con un poco de imaginación, ¡hasta se pueden sumar números! El truco consiste en agrupar las unidades, decenas, centenas, etc... de cada número por líneas, convertir los números en cuentas de un ábaco, operar con el ábaco y, al final, traducir el número expresado en el ábaco a base decimal. Por ejemplo, supongamos que recibimos la suma "34+577+1": a) Agrupar unidades, decenas, etc... por líneas: 471 37 5 b) Convertir los números en cuentas de un ábaco: oooooooooooo <-- 4+7+1 'o' oooooooooo <-- 3+7 'o' ooooo <-- 5 'o' c) Operar con el ábaco, es decir, reordenar las cuentas de modo que el ábaco represente un número en base decimal (no puede haber más de diez cuentas por línea): oo <-- 2 o <-- 1 oooooo <-- 6 d) Convertir el número expresado en el ábaco en base decimal: 612 Ahí va el algoritmo: s/$/+/ #1 -+- : x10 #2 | s/$/\n/ #3 | : siguiente #4 | s/\([0-9]\)\?+\(.*\)$/-\2\1/ #5 | t siguiente #6 | Labor a) y/-/+/ #7 | s/^+*\n// #8 | t numero-abaco #9 | b x10 #10 | : numero-abaco #11 -+- s/+\n// #12 | s/0//g #13 | s/1/o/g #14 | s/2/oo/g #15 | s/3/ooo/g #16 | s/4/oooo/g #17 | Labor b) s/5/ooooo/g #18 | s/6/oooooo/g #19 | s/7/ooooooo/g #20 | s/8/oooooooo/g #21 | s/9/ooooooooo/g #22 | : ordenar #23 -+- s/^\(.*\)o\{10\}\n\?/\1\no/ #24 | Labor c) t ordenar #25 -+- s/$/\n/ #26 | : abaco-numero #27 | s/^\n\([^0-9]*\)/\10/ #28 | s/^o\{1\}\n\([^0-9]*\)/\11/ #29 | s/^o\{2\}\n\([^0-9]*\)/\12/ #30 | s/^o\{3\}\n\([^0-9]*\)/\13/ #31 | s/^o\{4\}\n\([^0-9]*\)/\14/ #32 | Labor d) s/^o\{5\}\n\([^0-9]*\)/\15/ #33 | s/^o\{6\}\n\([^0-9]*\)/\16/ #34 | s/^o\{7\}\n\([^0-9]*\)/\17/ #35 | s/^o\{8\}\n\([^0-9]*\)/\18/ #36 | s/^o\{9\}\n\([^0-9]*\)/\19/ #37 | /^[0-9]/!b abaco-numero #38 -+- #1-#10 realizan la labor a): #1 añade un signo '+' al final de la línea, #3 añade una nueva línea al escritorio, el bucle #4-#6 extrae las cifras que anteceden a los signos '+' y las incluye en la última línea que abrimos con #3. Así pues, la primera vez que completemos este bucle la última línea contendrá las unidades de todos los números. Como consecuencia del bucle los signos '+' habrán pasado a convertirse a '-'. Para subsanarlo se usa #7. #8 intenta eliminar la primera línea suponiendo que todo son signos '+' lo que significaría que todas las cifras de los números ya han sido agrupadas correctamente y debemos continuar el al algoritmo (#9). Si la sustitución no ha tenido éxito, con #10 volvemos a #3 para añadir una nueva línea y repetir el proceso. #11-#22 realizan la labor b): cada cifra se sustituye por un número de oes igual al número que representa (1->'o',2->'oo', etc.). #23-#25 realizan la labor c): encontrar 10 cuentas en una línea supone sustituirlas por una cuenta en la línea inmediatamente inferior. El bucle continuará hasta que no queden grupos de 10 cuentas. #26-#38 realizan la labor d): #26 abre una línea al final del escritorio para ir almacenando el resultado, #28-#37 sustituyen las cuentas por su equivalente en número ('' -> 0, 'o' -> 1, etc.). Mientras no queden sólo números en el escritorio, #38 nos devuelve al inicio del bucle para seguir convirtiendo cuentas. 4.5. Emulación de comandos unix * tac (invierte el orden de las líneas de un fichero) 1!G #1 $q #2 h #3 d #4 #1 añade saca el contenido de la gaveta al final del escritorio. Con esto logramos que la nueva línea esté antes que las antiguas. Si hemos llegado a la última línea, acabamos imprimiendo (#2), en caso contrario guardamos el escritorio en la gaveta (#3) y comenzamos ciclo sin imprimir (#4). Si se usa la opción -n en la invocación de sed, podríamos ahorrarnos una línea. * wc -l (contar las líneas del fichero) sed -n '$=' fichero.txt * wc -c/wc -w Se propone llevar las cuentas con un ábaco que almacenamos en la gaveta. Cada vez que leamos una línea, convertimos todos sus caracteres a cuentas de ábaco, añadimos una cuenta más que indica el cambio de línea "\n" y reorganizamos las cuentas del ábaco. Cuando completamos la lectura del fichero, convertimos el número almacenado en forma de ábaco en un número en base decimal. y/./o/ #1 s/^/o/ #2 G #3 s/\n// #4 : ordenar #5 s/^\(.*\)o\{10\}\n\?/\1\no/ #6 t ordenar #7 $! { #8 h #9 d #10 } #11 s/$/\n/ #12 : abaco-numero #13 s/^\n\([^0-9]*\)/\10/ #14 s/^o\{1\}\n\([^0-9]*\)/\11/ #15 s/^o\{2\}\n\([^0-9]*\)/\12/ #16 s/^o\{3\}\n\([^0-9]*\)/\13/ #17 s/^o\{4\}\n\([^0-9]*\)/\14/ #18 s/^o\{5\}\n\([^0-9]*\)/\15/ #19 s/^o\{6\}\n\([^0-9]*\)/\16/ #20 s/^o\{7\}\n\([^0-9]*\)/\17/ #21 s/^o\{8\}\n\([^0-9]*\)/\18/ #22 s/^o\{9\}\n\([^0-9]*\)/\19/ #23 /^[0-9]/!b abaco-numero #24 #1 y #2 convierten en cuentas de ábaco ('o') los caracteres de la línea. #3 añade al final del escritorio el ábaco y #4 junta las cuentas correspondientes a la nueva línea con las cuentas que representan las unidades en el ábaco. El bucle #4-#6 convierte diez cuentas de un determinado nivel del ábaco en una sola cuenta en el nivel superior. Si no se ha llegado a la última línea, se guarda el ábaco en la gaveta y se comienza un nuevo ciclo sin imprimir. Por contra, sí ya no quedan más líneas por leer, #12-#24 convierten el número en formato ábaco en un número en base decimal, que se imprimirá al acabar el ciclo. Obsérvese que este mismo algoritmo nos sirve para emular wc -w. Basta con sustituir #1 y #2 por una orden que convierta en cuenta cada palabra: s/[^ \t]\+/o/g s/[^o]*//g E, incluso, para contar la presencia de cualquier patrón: s/patrón/\n/g s/[^\n]//g y/\ /o/ * cat -n (listar un fichero anteponiendo a cada línea su número). cat -b Es necesario conservar y presentar el texto de la línea de modo que no se puede usar '=', porque esta orden manda el número de línea al flujo de salida, no al escritorio. Por ello, es imposible hacer ninguna manipulación para lograr tener en una misma línea el número y el texto. Casi todo este algoritmo se basa en lo realizado para resolver aquel que proponía sumar uno a los números que contenían las líneas de un determinado fichero. x #1 s/^9*$/0&/ #2 s/.9*$/x&/ #3 G #4 h #5 s/[0-9]*x\([0-9]*\).*$/\1/ #6 y/0123456789/1234567890/ #7 G #8 s/\n.*\n/ / #9 x #10 s/x.*// #11 G #12 s/\n// #13 h #14 s/ .*$// #15 x #16 #1 trae el número de línea a la gaveta. #2 añade un 0 a los números constituidos exclusivamente por nueves. Obsérvese que cuando la gaveta está vacía, hay un conjunto de 0 nueves y, por tanto, se incluye un 0; de modo que no es necesario tratar de forma particular este caso. #3 separa la parte variante de la invariante. A continuación, #4 añade la línea leída al final del escritorio y #5 copia todo en la gaveta. #6 deja en el escritorio sólo la parte variante. #7 realiza la transformación de esta parte, #8 y #9 nos permiten incluir la línea leída después de la parte variante (la separación que se ha usado es un espacio). Como aún nos falta la parte invariante, #10 intercambia los contenidos de gaveta y escritorio. #11 deja sólo la parte invariante y #12 y #13 pegan detrás la parte variante y la línea. Ya está hecho todo. Sin embargo, hemos de guardar el número de línea en la gaveta para el siguiente ciclo. Por ello, #14 copia el escritorio en la gaveta, #15 elimina el texto de la línea y, por último, #16 intercambia los contenidos de la gaveta (número+texto) y el escritorio (número). Si lo que se quiere emular es cat -b, es decir, numerar las líneas excepto las vacías, basta con incluir al principio del guión la orden: /^$/b que evitaría la ejecución del algoritmo, si la línea leída está vacía. * unix2dos (eliminar el retorno de carro) sed 's/^M$//' fichero-en-unix.txt > fichero-en-dos.txt ^M Se consigue pulsando Ctrl+V Ctrl+M. * dos2unix (añadir retorno de carro al final de la línea) sed 's/$/^M/' fichero-en-dos.txt > fichero-en-unix.txt ^M Se consigue pulsando Ctrl+V Ctrl+M.