ed(1) es La Herramienta Correcta ================================ (Traducido del inglés gopher://katolaz.net/0/ed_tutorial.txt) Ya sabes esto: ed(1) es el editor de texto estándar [1] y, de hecho, encontrará­a ed(1) instalado en cualquier símil de UNIX. Pero pienso que la mayoría de los usuarios de UNIX nunca han intentado abrirlo realmente. La razón es probablemente que ed(1) es considerado "difícil de usar" y "poco amigable con el usuario". De verdad, no veo razón para tal percepción pero entiendo la motivación de su popularidad, así­ que voy a intentar convencerlos con ejemplos concretos y evidencia científica de que ed(1) es, casi siempre, La Herramienta Correcta. Este tutorial fue publicado originalmente como una serie de publicaciones de "phlog" (un blog en gopher) con la esperanza de servir como una manual para principiantes. Las ocho publicaciones son republicadas abajo en el mismo orden en que aparecieron en el phlog. La única adición es el último párrafo de la sección VII que no estaba presente en la serie inicial de phlogs. Las referencias de cada sección son señaladas al final de la misma. Si estás apurado y te gustaría­a leer un libro tradicional, busca el gran "Ed Mastery" de Michael Lucas [2]. KatolaZ ed(1) es La Herramienta Correcta (I) ================================ Lo primero es lo primero: ed(1) es un editor lineal, lo que quiere decir que actúa una línea a la vez. Esto es algo a lo que ya no estamos acostumbrados, dado que nuestras terminales de cristal han permitido evolucionar a los editores de caracteres en pantalla completa. Pero en los teletipos la edición lineal tenía todo el sentido del mundo. Esto significa que necesitamos decirle a ed(1) qué línea nos gustaría editar y qué hacer en qué línea. En este tutorial vamos a usar ed(1) para compilar y administrar listas de tareas sencillas. Esto nos va a permitir explorar casi todos los comandos de ed(1). Comencemos por crear el archivo "TODO.txt" con ed(1): $ ed TODO.txt TODO.txt: No such file or directory Bueno, bienvenido a ed(1), también conocido como "El fiel siervo silencioso de los antaño". ed(1) está diciéndote que no pudo encontrar ningún archivo llamado TODO.txt en el directorio actual (y esto es algo que ya sabíamos) y entonces imprime una nueva línea para señalar que se encuentra listo para recibir comandos. Sí, ed(1) no tiene una señal de intérprete por defecto. En realidad sí la tiene, y es simplemente una línea en blanco. Ahora agreguemos unos pocos items a nuestra lista de tareas TODO.txt: 0a LISTA DE TAREAS - Hacer un phlog sobre ed(1) - Sacar la basura - Preparar un té - Revisar un problema con postgrey La primera línea "0a" le indica a ed(1) que quiero "a-gregar" líneas luego de la línea 0. Ahora, la línea 0 realmente indica el comienzo del búfer. ed(1) acepta mi comando y espera que las líneas sean agregadas. Va a continuar recibiendo líneas hasta que introduzcas una línea que contenga sólo un "." (punto) [3]. Entonces ed(1) va a "imprimir" su señal (que, como ves arriba es una línea en blanco) y va a esperar por el siguiente comando. Vamos a pedirle a ed(1) que "im(p)rima" la lista completa entonces: ,p LISTA DE TAREAS - Hacer un phlog sobre ed(1) - Sacar la basura - Preparar un té - Revisar un problema con postgrey (nota la señal del intérprete de ed(1) en la última línea). El comando "p" se usa para imprimir líneas. Normalmente se pone después de la especificación de un rango de líneas, pero en este caso "," indica el rango "desde la primera línea hasta la última". ¿Ahora qué pasa si digitas la "p" por sí­ sola? p - Revisar un problema con postgrey (nota otra vez la señal de ed(1) sobre esta línea). Bueno, cualquier comando en ed(1) modifica la "dirección de línea actual", que es la línea en que trabajará­a cualquier comando de ed(1) implícitamente si no le indicas la línea. El comando "p" mueve la dirección actual a la última línea que imprimió. Así­ que ",p" movió la dirección actual a la última línea del búfer, y la siguiente "p" simplemente imprimió la línea actual (la última). Otro comando útil es "n", que imprime líneas añadiendo el número de línea: ,n 1 LISTA DE TAREAS 2 - Hacer un phlog sobre ed(1) 3 - Sacar la basura 4 - Preparar un té 5 - Revisar un problema con postgrey Ahora eso sí que es conveniente. Adivina cómo le pedirá­a a ed(1) que imprima la tercera línea en tu búfer: 3p - Sacar la basura Asiimismo, puedes usar el marcador especial "$" para indicar "la última línea en el búfer": $n 5 - Revisar un problema con postgrey Nota que no hay espacio entre los rangos de líneas y el comando que les aplica. Creo que has tenido suficiente ed(1)-ción por hoy, y ya no estoy de ánimo para comenzar a trabajar en mi lista de tareas ahora. Así que sólo guardemos nuestro trabajo para más tarde: w 116 ed(1) imprime el número de bytes escritos al archivo TODO.txt. Ahora podemos salir de ed(1): q $ y ya estás de vuelta en el intérprete de comandos de la terminal. Ahora puedes ver tu nueva lista de tareas con: $ cat TODO.txt LISTA DE TAREAS - Hacer un phlog sobre ed(1) - Sacar la basura - Preparar un té - Revisar un problema con postgrey $ o usando: $ printf ",p\n" | ed TODO.txt 116 LISTA DE TAREAS - Hacer un phlog sobre ed(1) - Sacar la basura - Preparar un té - Revisar un problema con postgrey $ ;-) -+-+-+- ed(1) fue incluído en UNIX-V1 (1971) y fue utilizado para escribir el kernel de Unix y todos los programas distribuidos con Unix al menos hasta Unix-V7 (1979) -+-+-+- [1] https://www.gnu.org/fun/jokes/ed-msg.html [2] https://www.tiltedwindmillpress.com/product/ed/ [3] Uso un guión "-" para indicar tareas que aún están pendientes, un más "+" para tareas que sigo haciendo y un asterisco "*" para las completadas. ed(1) es La Herramienta Correcta (II) ===================================== Continuemos trabajando en nuestra misión de aprender lo básico de ed(1), el editor de texto estándar del entorno Unix. En la primera entrega de la miniserie de phlogs [1] vimos unos cuantos comandos básicos para agregar algunas líneas ("a"), imprimir líneas ("p", "n") y guardar un archivo luego de terminar ("w"). Usando aquellos comandos fuimos capaces de recopilar una simple lista de tareas: $ ed TODO.txt 116 ,n 1 LISTA DE TAREAS 2 - Hacer un phlog sobre ed(1) 3 - Sacar la basura 4 - Preparar un té 5 - Revisar un problema con postgrey Ahora imaginemos que queremos insertar un nuevo item (comprar té) justo antes de la línea que nos recuerda hacer té. Vamos a usar el comando "i" (para "insertar"): 4i - Comprar pé . ,n 1 LISTA DE TAREAS 2 - Hacer un phlog sobre ed(1) 3 - Sacar la basura 4 - Comprar pé 5 - Preparar un té 6 - Revisar un problema con postgrey ¿Fácil, verdad? Si "a" es utilizada para "agregar" líneas después de una línea dada, "i" es usada, por el contrario, para "insertar" líneas antes de una línea especificada. Como con "a", los comandos "i" terminan en una línea que contenga solamente un ".". Si alguna vez has usado vi(1) o uno de sus múltiples clones, sabes que "i" es también el comando necesario para cambiar al modo "insertar" en vi(1). Bueno, esto no es una coincidencia. Nota que cuando insertamos la nueva línea también introdujimos un error de tipeo: Definitivamente lo que queremos es comprar algo de "té" (no "pé"). Podemos "cambiar el contenido de una línea usando el comando "c": 4c - Comprar té . ,n 1 LISTA DE TAREAS 2 - Hacer un phlog sobre ed(1) 3 - Sacar la basura 4 - Comprar té 5 - Preparar un té 6 - Revisar un problema con postgrey Nota que ed(1) actúa de manera consistente: Los tres comandos usados para agregar, insertar o cambiar líneas finalizan todos por una línea conteniendo un solo ".". De hecho, el comando "c" en general nos permite reemplazar una línea (o un rango de líneas) con otra línea (o rango de líneas). Así­ que si prefieres café en vez de té podrías usar: 4,5c - Comprar café - Comprar azúcar - Hacer un café . ,n 1 LISTA DE TAREAS 2 - Hacer un phlog sobre ed(1) 3 - Sacar la basura 4 - Comprar café 5 - Comprar azúcar 6 - Hacer un café 7 - Revisar un problema con postgrey Bueno, es evidente que tener que reescribir una línea sólo para corregir un error de tipeo no es exactamente lo que uno llamaría una sesión eficiente de edición. De hecho, el comando "c" se utiliza mayormente cuando el contenido de un rango de líneas tiene que ser modificado sustancialmente. Afortunadamente, ed(1) tiene otro comando ("s", de "sustituto") lo cual permite corregir fácilmente errores de tipeo, además de realizar sustituciones complejas a través de un rango. Ese va a ser el foco del siguiente phlog en esta serie :) -+-+-+- [1] gopher://republic.circumlunar.space/0/~katolaz/phlog/20190505_ed_lists.t xt ed(1) es La Herramienta Correcta (III) ===================================== Una lista de tareas es útil si empiezas a trabajar en los items y al tiempo eres capaz de eliminar algunos items de la lista, tarde o temprano... Nuestra lista actual se ve así­: ,n 1 LISTA DE TAREAS 2 - Hacer un phlog sobre ed(1) 3 - Sacar la basura 4 - Comprar café 5 - Comprar azúcar 6 - Hacer un café 7 - Revisar un problema con postgrey Ahora está claro que he empezado a publicar phlogs sobre ed(1), así­ que el primer item debería ser actualizado de alguna forma. Se me ocurrió una simple convención para identificar el estado de los items en una lista de tareas que es usar el primer caracter del item: un "-" para indicar un item por realizar, un "+" que indica una tarea en la que he empezado a trabajar, y un "*" que marca un item completado. En este caso, me gustaría cambiar el "-" en el primer item con un "+". Comencemos por buscar la línea en que mencioné la palabra "phlog": /phlog/ - Hacer un phlog sobre ed(1) Sí­, una expresión como "/expr/" es empleada en ed(1) para buscar un patrón. ed(1) busca la primera ocurrencia de aquel patrón y la imprime. La mayoría de las versiones de ed(1) dan la vuelta al buscar y empiezan desde el inicio si es que alcanzaron el fin del documento. Ahora la línea actual (lela asi llamada ".") es la que contiene el item que quiero editar. "Substituyamos" el primer "-" en la línea con un "+": s/-/+/ p + Hacer un phlog sobre ed(1) Sí, es así de simple. El comando "s" (para "sustituir") reemplaza la primera ocurrencia del patrón entre el primer par de "/" siguiéndolas con el patrón entre el segundo par de "/" que viene después. En este caso, simplemente reemplazamos "-" por "+". El comando "s" acepta una línea (o rango de líneas) en las que trabajar. Así que si queremos trabajar en todos los items de la lista de tareas, puedes cambiar su estado con un solo comando: 1,$s/-/+/ ,p LISTA DE TAREAS + Hacer un phlog sobre ed(1) + Sacar la basura + Comprar café + Comprar azúcar + Hacer un café + Revisar un problema con postgrey Eso fue potente, ¿verdad? Pero en mi caso, bueno, no es verdad, dado que no he terminado de sacado la basura y algunos otros items están en otro estado. Revirtamos nuestro último cambio: u ,p LISTA DE TAREAS + Hacer un phlog sobre ed(1) - Sacar la basura - Comprar café - Comprar azúcar - Hacer un café - Revisar un problema con postgrey El comando "u" (de "undo" en inglés para "deshacer") hará eso (ej. revertir el efecto del último comando). Intenta ver qué pasa si sigues presionando "u" :) Lo genial acerca de "s" es que la línea en que tiene que actuar puede expresarse con un patrón. Por ejemplo, resolver el problema en postgrey así que puedo cambiar el estado de la línea que contiene "postgrey" a "listo" reemplazando el "-" por un "*". Pero no recuerdo en qué línea está, así que puedo usar el comando: /postgrey/s/-/*/ ,p LISTA DE TAREAS + Hacer un phlog sobre ed(1) - Sacar la basura - Comprar café - Comprar azúcar - Hacer un café * Revisar un problema con postgrey Recuerda que el "." se ha fijado en la última línea editada: . * Revisar un problema con postgrey Ahora que completamos un item, por fin podemos celebrar: ¡Hurra! Bueno, todavía hay un montón de cosas que hacer de cualquier manera. Cómo pasamos al siguiente item pendiente en la lista? Fácil: Buscamos un "-": /-/ - Sacar la basura Sí, ya sé, pero este no es el día de la basura. Busquemos el siguiente pendiente: // - Comprar café Sí. La rápida combinación "//" repite la última búsqueda que hiciste. Y podemos seguir: // - Comprar azúcar // - Hacer un café Eso sí que es conveniente, eespecialmente ya que "/" normalmente queda en una posición cómoda en muchas distribuciones de teclados. El último ejemplo: Tengo que ser honesto contigo: No puedo tomar nada de café, así que me gustaría reemplazar cualquier ocurrencia de "café" por "té": /café/s/café/té/ p - Comprar té El comando "/café/s/café/té" se tiene que leer como "busca la siguiente línea que contenga 'café' y reemplaza 'café' por 'té' en esa línea. Entonces ¿qué pasó acá? Echemos un vistazo al archivo completo: ,n 1 LISTA DE TAREAS 2 + Hacer un phlog sobre ed(1) 3 - Sacar la basura 4 - Comprar té 5 - Comprar azúcar 6 - Hacer un café 7 * Revisar un problema con postgrey Entonces, la última búsqueda que realizamos ("//") había puesto el punto en la línea 6 ("- Hacer un café"). Cuando enviamos el comando de sustitución, ed(1) tuvo que buscar la siguiente ocurrencia de "café". Dio la vuelta (dado que no hay ninguna línea que contenga "café" después de la línea 6), encontró "café" en la línea 4 y reemplazó "café" con "té" en esa línea. Nos gustaría repetir lel último comando "s" entonces para reemplazar la otra ocurrencia de la palabra "café": /café/s/café/té/ . - Hacer un té Muy bien, eso es potente, pero reingresar el comando no es divertido. De cualquier forma, no podemos aprender todo en una sola entrada de phlog, y esta es ya bastante larga. No nos olvidemos de guardar nuestro trabajo: w 145 Siendo honesto contigo, la verdad me gustaría deshacerme de esa línea con "azúcar" ya que nunca le pongo azúcar al té. Pero eso es una aventura para otro día :) ed(1) es La Herramienta Correcta (IV) ========================================= Echémosle un vistazo a nuestra lista de tareas actual: $ ed TODO.txt 145 ,p LISTA DE TAREAS + Hacer un phlog sobre ed(1) - Sacar la basura - Comprar té - Comprar azúcar - Hacer un té * Revisar un problema con postgrey Necesito añadir un par de items a la lista. Hagámoslo: a - encontrar el frasco de azúcar - configurar el correo de Unix V7 - leer Shismatrix . Hay un error de tipeo: s/Shi/Schi/ . - leer Schismatrix Entonces como dije en el último phlog, de verdad, me gustaría deshacerme del azúcar. En serio, necesito "borrar" todas las líneas que contienen "azúcar" (¡y acabo de meter otra más!). La "i" es para "insertar", la "a" agrega, la "c" cambia, la "p" imPrime y la "d" borra (que es "delete" en inglés), y requiere o una direcciòn o un patrón. Hagamos una prueba: /azúcar/d ,n LISTA DE TAREAS + Hacer un phlog sobre ed(1) - Sacar la basura - Comprar té - Hacer un té * revisar un problema con postgrey - configurar el correo de Unix V7 - leer Schismatrix Genial, la línea que ponía "- Comprar azúcar" ha desaparecido. Deshagamos el cambio e intentemos algo más: u 5d ,n 1 LISTA DE TAREAS 2 + Hacer un phlog sobre ed(1) 3 - Sacar la basura 4 - Comprar té 5 - Hacer un té 6 * revisar un problema con postgrey 7 - encontrar el frasco de azúcar 8 - configurar el correo de Unix V7 9 - leer Schismatrix ¡El mismo resultado! Así que puedes borrar una línea usando el comando "d" y especificar o una línea o un patrón que debería coincidir con la línea. Excelente, pero a veces es impráctico, sobre todo si tienes mucha "azúcar" de la que deshacerte. Obviamente hay una forma más ed(1)-ficiente de lidiar con esto. Para tener algo no trivial con qué trabajar (por ejemplo, más de una línea con azúcar), primero deshagamos el cambio para que todo el "azúcar" reaparezca. u ,n LISTA DE TAREAS + Hacer un phlog sobre ed(1) - Sacar la basura - Comprar té - Comprar azúcar - Hacer un té * revisar un problema con postgrey - encontrar el frasco de azúcar - configurar el correo de Unix V7 - leer Schismatrix ¿Y qué tal si queremos imprimir sólo las líneas que contienen "azúcar"? Sabemos que el comando de búsqueda "/azúcar/" no va a funcionar, dado que imprime sólo la siguiente línea y luego se detiene. Así que probemos algo más potente: g/azúcar/p - Comprar azúcar - encontrar el frasco de azúcar Acabamos de usar el comando de ed(1) "global", indicado por la letra "g". Este comando busca coincidencias del patrón inmediatamente posterior a él (en este caso "azúcar") y entonces ejecuta el comando indicado posteriormente (en este caso, el comando es "p" de imprimir). Así que lo que acabamos de hacer es una instancia específica de "g/RE/p", que busca todas las líneas coincidentes con la expresión regular "RE" y las imprime en pantalla. ¡Sí, este es justamente el origen del comando de Unix grep(1)! [1] Lo bonito sobre el comando global es que puede ser acompañado por literalmente cualquier otro comando de ed(1). Así que si necesitamos borrar todas las líneas que contienen "azúcar", simplemente le damos: g/azúcar/d ,n 1 LISTA DE TAREAS 2 + Hacer un phlog sobre ed(1) 3 - Sacar la basura 4 - Comprar té 5 - Hacer un té 6 * revisar un problema con postgrey 7 - configurar el correo de Unix V7 8 - leer Schismatrix Ya, ahora nos estamos entendiendo. Eso podría haberse convertido en el comando "gred" pero los sabios en Murray Hill notaron que habría sido mejor tener una herramienta genérica para manejar edición orientada a líneas y nació el comando de Unix sed(1) en su lugar [2]. Antes de que guardemos la lista de tareas, necesito hacer notar que he comenzado a trabajar en dos items, aquellos en la línea 7 y 8 (sí, realmente estoy trabajando en un servidor con Unix V7 para uso general y necesito un buen nombre para él, posiblemente del universo de Schismatrix :P): g/ix/s/-/+ 1 LISTA DE TAREAS 2 + Hacer un phlog sobre ed(1) 3 - Sacar la basura 4 - Comprar té 5 - Hacer un té 6 * revisar un problema con postgrey 7 + configurar el correo de Unix V7 8 + leer Schismatrix ¿Puedes ver por qué el comando global hizo exactamente lo que esperamos que hiciera? Diseccionémoslo. Le pedimos a ed(1) buscar globalmente el patrón "ix" /g/ix/), y sustituir un "-" por un "+" en las líneas que coincidan (s/-/+/). ¿Fácil, cierto? Guardemos nuestro trabajo ahora. w 180 q -+-+-+- [1] http://www.catb.org/~esr/jargon/html/G/grep.html [2] http://www.cs.dartmouth.edu/~doug/reader.pdf ed(1) es La Herramienta Correcta (V) ======================================= Esta entrada va a ser relativamente corta. Recientemente usé ed(1) para escribir mi entrada para el concurso de la ROOPHLOCH 2020 [1] y lo hice desde la versión de ed(1) disponible en FUZIX [2], la cual es algo más cercana al ed(1) original disponible en los primeros sistemas Unix de investigación. No hace falta decir que ed(1) hizo su trabajo como se esperaba (obviamente un editor no sabe nada sobre la cantidad de tonterías que pones en un archivo así que me refiero al mecanismo de edición, no al contenido :P). Luego de finalizar mi publicación, la traspasé a fold(1) para asegurarme de que todas las líneas fueran de como mucho 68 caracteres de largo. Escogí fold(1) porque es el único formateador que tenía en FUZIX (y en realidad lo tuve que portar a FUZIX desde sbase [3], porque FUZIX no tiene fold(1) todavía). Y entonces recordé que algo parecido a: $ fold -w 68 -s file.txt se aseguraría de que ninguna de las líneas sea más ancha que 68 caracteres, además de impedir quebrar palabras (-s), pero no uniría líneas que pertenezcan al mismo párrafo, como por ejemplo, fmt(1) o par(1). Eso quiere decir que todos mis párrafos estaban desordenados, con un montón de líneas indeseables donde se encontraban las líneas originales. ¿La solución? ¡Simplemente unir todas las líneas de un párrafo en una única línea! La forma directa de hacerlo es empleando el comando de ed(1) "j" (sí, "j" es para unir), que va a unir la línea actual y la siguiente y deja la dirección actual en la línea resultante. Hice esto repitiendo el comando "j" tanto como me hizo falta para tener cada párrafo en una sola línea (hay por supuesto una forma más inteligente de hacer esto, pero necesitamos algo más de conocimientos de ed(1)). Entonces guardamos el archivo, se lo pasamos a fold y luego cat(1) a un puerto serial de mi portátil, listo para ser enviado por Internet. Aprenderemos una forma mucho más rápida de usar fold(1) dentro de ed(1), pero antes de eso necesitamos echarle un vistazo a unos cuantos comandos de ed(1) más. -+-+-+- [1] gopher://zaibatsu.circumlunar.space:70/1/~solderpunk/roophloch/2020 [2] http://www.fuzix.org [3] http://git.suckless.org/sbase/ ed(1) es La Herramienta Correcta (VI) ======================================== Así que continuemos desde donde lo dejamos. Hemos completado una lista de tareas con ed(1): $ cat TODO.txt LISTA DE TAREAS + Hacer un phlog sobre ed(1) - Sacar la basura - Comprar té - Hacer un té * revisar un problema con postgrey + configurar el correo de Unix V7 + leer Schismatrix y aprendimos un montón de comandos sencillos de ed(1) hasta ahora. De hecho, ha pasado bastante tiempo desde que creamos esta lista de tareas, y muchos items han sido tratados y pueden marcarse como "listos". En particular, yo he phlogeado claramente (un poco) sobre ed(1). He sacado la basura (al menos un par de veces desde que empezamos esta serie de phlogs), y me hice muchos tés. Queremos nuestra lista de tareas para reflejar este progreso: $ ed TODO.txt 180 /phlog/s/^+/*/ 3s/^-/*/ /Hacer/s/^-/*/ 1,$p LISTA DE TAREAS * Hacer un phlog sobre ed(1) * Sacar la basura - Comprar té * Hacer un té * revisar un problema con postgrey + configurar el correo de Unix V7 + leer Schismatrix Perfecto, eso está un poco mejor. He marcado todas las tareas "completadas" con un "*" usando diferentes métodos para tratar con las líneas correspondientes. El principal problema es que me gustaría mantener todas las tareas pendientes al inicio de la lista de tareas. ¿Cómo hacemos eso? Lo que nos gustaría hacer es mover la línea 4 (- Comprar té) al inicio de la lista, o sea, luego de la línea que dice "LISTA DE TAREAS". Fácil: 4m1 1,$p LISTA DE TAREAS - Comprar té * Hacer un phlog sobre ed(1) * Sacar la basura * Hacer un té * revisar un problema con postgrey + configurar el correo de Unix V7 + leer Schismatrix ¡Guau! Eso es todo, pero ¿cuál es la magia? Nada de magia. Si quieres mover la línea a otro lado, usa el comando "m". El comando "m" puede ser precedido por una dirección, un rango, o una expresión regular y es seguida por una dirección. Pondrá las líneas especificadas detrás de "m" luego de la dirección especificada después de "m", entonces: 4m1 significa mover la línea 4 después de la línea 1. Esto es exactamente lo que hizo "m". Ahora bien, también haría sentido poner todas las tareas completadas al final del archivo, dado que no tenemos que trabajar en ellas más. Bueno, dado que todas las tareas completadas son líneas que comienzan con "*", esto es muy fácil de lograr: g/^\*/m$ 1,$p LISTA DE TAREAS - Comprar té + configurar el correo de Unix V7 + leer Schismatrix * Hacer un phlog sobre ed(1) * Sacar la basura * Hacer un té * revisar un problema con postgrey y esto lo logramos diciéndole a ed(1) que tome todas (g) las líneas que comiencen con "*" (/^\*/), y moverlas (m) después de la última línea ($). Fíjate en que tuvimos que entrecomillar el caracter "*" dado que también es utilizado como un comodín en expresiones regulares, mientras que aquí nos referimos a un "*" literal. ¿Ahora qué tal si queremos copiar una línea en vez de moverla? Si pensaste que el comando "copiar" sería "c" piensa de nuevo porque ya vimos que "c" es el comando para "cambiar" una línea existente [1]. El comando para copiar líneas es "t" de "transferir" y su sintaxis es identica a la de "m". Recién me acordé de que necesito comprar algunos tomates: /té/t 1,$p LISTA DE TAREAS - Comprar té + configurar el correo de Unix V7 + leer Schismatrix * Hacer un phlog sobre ed(1) * Sacar la basura * Hacer un té * revisar un problema con postgrey - Comprar té ¿Bueno, y qué pasó acá? Introduje el comando "/té/t" que quiere decir "busca la primera línea que coincida con "té" y cópiala... ¿Dónde?". Y bueno, recuerda que la consistencia en ed(1) es clave: Si una dirección no se explicita, entonces ed(1) va a asumir que te refieres a la "línea actual". En este caso, la línea actual cuando escribimos el comando "t" era la última línea en el archivo (dado que le dimos el comando "1,$p"). Así que "t" hizo lo correcto y copió la línea coincidente luego de la última línea del archivo. Ahora sólo reemplazamos "té" con tomates: s/é/omates/ y entonces mueve todas las líneas con pendientes al inicio del archivo: g/^-/m1 1,$p LISTA DE TAREAS - Comprar tomates - Comprar té + configurar el correo de Unix V7 + leer Schismatrix * Hacer un phlog sobre ed(1) * Sacar la basura * Hacer un té * revisar un problema con postgrey Súper fácil, ¿cierto? Como siempre, guardemos nuestra lista de tareas, hasta la próxima: w 210 q -+-+-+- [1] gopher://republic.circumlunar.space/0/~katolaz/phlog/20190830_ed_lists_2 .txt ed(1) es La Herramienta Correcta (VII) ========================================== Lo lindo acerca de las listas de tareas es que en algún momento serás capaz de pasar por todas las tareas y tarjar todas las tareas completadas. Pero a veces es bueno mantener un seguimiento de las cosas que has conseguido a través de una lista de tareas. Así que normalmente guardo las tareas finalizadas en un archivo "BIEN-HECHO" para darme una palmadita en la espalda ficticia. Démosle un vistazo a nuestra lista de tareas actual: $ ed TODO.txt 210 1,$p LISTA DE TAREAS - Comprar tomates - Comprar té + configurar el correo de Unix V7 + leer Schismatrix * Hacer un phlog sobre ed(1) * Sacar la basura * Hacer un té * revisar un problema con postgrey Dada nuestra elección, todas las líneas que comienzan con "*" denotan tareas completadas. Si queremos copiarlas todas a un archivo llamado "BIEN-HECHO", ed(1) sabe de uno o varios trucos: 6,9w BIEN-HECHO 98 Debemos ser capaces de entender qué podría haber pasado acá empleando nuestros conocimientos sobre ed(1). "6,9" especifica un conjunto de líneas (todas las líneas entre 6 y 9, ambas incluídas), mientras que "w" es el comando para grabar el búfer de ed(1) a un archivo. También hemos visto que "w" seguido por un nombre de archivo vuelca todo el contenido actual del búfer de ed(1) en ese archivo. Bueno, eso es exactamente lo que pasó acá, sólo que le pedimos a ed(1) que volcara un rango de líneas en vez del búfer completo, y el archivo de destino es BIEN-HECHO. Podemos revisar si nuestra intuición es correcta saliendo de ed(1) y usando cat(1) en el archivo BIEN-HECHO. La buena noticia es que ni siquiera necesitamos salir de ed(1): !cat BIEN-HECHO * Hacer un phlog sobre ed(1) * Sacar la basura * Hacer un té * revisar un problema con postgrey ! Espera, ¿pero qué fue eso? A ver, probemos de nuevo: !pwd /home/katolaz/tmp ! Esto no puede ser lo que creemos ¿o sí? !ls -l total 8 -rw-r--r-- 1 katolaz katolaz 210 Oct 10 10:39 TODO.txt -rw-r--r-- 1 katolaz katolaz 98 Oct 11 05:35 BIEN-HECHO ! Listo, ya, ¡lo entendimos! ed(1) usa el comando "!" para accionar la consola con otro comando, por ejemplo, para ejecutar un comando de nuestra elección como si fuera tipear dentro del intérprete de la terminal. El comando "!" va a ejecutar el comando que escribiste, va a mostrar su salida en la pantalla (ojo, sólo en la pantalla, sin afectar tu búfer actual) y finaliza con un "!" en una línea. En este punto, sabemos que el comando externo ha terminado y ed(1) está listo para recibir más comandos: 1,$p LISTA DE TAREAS - Comprar tomates - Comprar té + configurar el correo de Unix V7 + leer Schismatrix * Hacer un phlog sobre ed(1) * Sacar la basura * Hacer un té * revisar un problema con postgrey Muy conveniente, ¿verdad? Fíjate de nuevo en que la invocación de comandos externos a través de "!" no modifica el búfer actual. Así que hemos grabado exitosamente las líneas 6-9 al archivo "BIEN-HECHO" en el directorio actual. Ahora las quitamos del TODO.txt: g/^\* /d 1,$p LISTA DE TAREAS - Comprar tomates - Comprar té + configurar el correo de Unix V7 + leer Schismatrix para mantener nuestra lista de tareas ordenada. También hice mi lista de la compra ayer, así que: g/Comprar/s/^-/\*/ 1,$p LISTA DE TAREAS * Comprar tomates * Comprar té + configurar el correo de Unix V7 + leer Schismatrix pero ahora me gustaría agregar las nuevas tareas al archivo "BIEN-HECHO". Si usáramos (no lo hagas): 2,3w BIEN-HECHO ed(1) destruiría silenciosamente el contenido anterior de BIEN-HECHO y pondría allí las dos líneas que acabamos de completar. Pero eso no es lo que queremos: Recuerda mantener un registro de las cosas que has hecho es un buen motivador. El comando de ed(1) para "agregar" líneas al final de un archivo ya existente es "W": 2,3W BIEN-HECHO 32 !cat BIEN-HECHO * Hacer un phlog sobre ed(1) * Sacar la basura * Hacer un té * revisar un problema con postgrey * Comprar tomates * Comprar té ! Podemos quitar las tareas completadas y guarar nuestro archivo TODO.txt: 2,3d w 1,$p 69 LISTA DE TAREAS + configurar el correo de Unix V7 + leer Schismatrix ¿Y si queremos reinsertar nuestras viejas tareas (completadas) en algún lugar de nuestro archivo TODO.txt, por ejemplo, luego de la línea 1? Simple: 1r BIEN-HECHO 129 1,$p LISTA DE TAREAS * Hacer un phlog sobre ed(1) * Sacar la basura * Hacer un té * revisar un problema con postgrey * Comprar tomates * Comprar té + configurar el correo de Unix V7 + leer Schismatrix Y nuestras viejas tareas (las completadas) están de vuelta. Pero no, realmente no necesitamos verlas aquí: u 1,$p LISTA DE TAREAS + configurar el correo de Unix V7 + leer Schismatrix w 69 q Nota que en las versiones de ed(1) actualmente disponible en la mayoría de los sistemas operativos (incluyendo Linux, OpenBSD, FreeBSD, NetBSD), el comando "r" también puede leer líneas de un comando externo dentro de un búfer de ed(1). Por ejemplo: $ ed filelist filelist: No such file or directory r ! ls -l 130 total 8 -rw-r--r-- 1 katolaz katolaz 57 Oct 11 06:01 TODO.txt -rw-r--r-- 1 katolaz katolaz 119 Oct 11 06:00 BIEN-HECHO lo que a veces es muy conveniente. Esta opción no estaba disponible en las versiones originales de ed(1) hasta UNIXv7. En tal caso, usarías un simple truco: $ ed filelist filelist: No such file or directory ! ls -l > tmp ! r tmp 183 1,$p total 8 -rw-r--r-- 1 katolaz katolaz 57 Oct 11 06:01 TODO.txt -rw-r--r-- 1 katolaz katolaz 119 Oct 11 06:00 BIEN-HECHO -rw-r--r-- 1 katolaz katolaz 0 Oct 11 06:06 tmp que (casi) hace lo mismo. Ahora no debería ser difícil para ti descubrir cómo usar el comando "!" para usar fold(1) en un búfer. Esto requiere sólo un poco de ed-fu. Asumamos que has escrito un mensaje de correo con ed(1) y que ahora quieres dejarlo en 72 caracteres: w e ! fold -s -w 72 % Debes ser capaz de descifrar este comando fácilmente. Estamos grabando el búfer actual primero ("w") y entonces cargamos en el búfer actual ("e") la salida del comando "fold -s -w 72 %". Obviamente, hay algo de magia de ed(1) involucrada: de hecho, el caracter "%" es reemplazado por ed(1) con el nombre del archivo actual. Así que el resultado del comando es reemplazar el búfer actual con el de fold(1). Genial, ¿cierto? -+-+-+- ed(1) es La Herramienta Correcta (VIII) =========================================== Si estás leyendo este tutorial, eso significa que tuviste la paciencia y determinación para recorrer este duro camino de aprender lo básico de ed(1). Lo hiciste bien, pero no te voy a felicitar. De hecho por aprender lo básico de ed(1) no has logrado nada muy especial: por casi dos décadas, aprender ed(1) era lo primero que cualquier novato que quería jugar seriamente con Unix tenía que hacer. Aprendías ed(1) primero y entonces podías jugar con el código fuente y escribir tus propios programas. Entonces, las terminales acristaladas hicieron la concisión minimalista de ed(1) innecesaria, y más potentes editores hicieron su aparición en sistemas Unix tales como emacs y vi. (sí, vi(1), no vim(1). vim(1) vino mucho más tarde). En este punto, ed(1) comenzó a quedar olvidado, dado que los novatos del mundo de Unix no lo necesitaban para sus fantásticos proyectos, y esta pequeña herramienta adquirió la (injustificada) fama de ser difícil de usar y difícil de aprender. Espero que este tutorial te haya mostrado que ed(1) es una herramienta potencialmente muy útil que no es tan difícil de usar o de aprender. Es obviamente una herramienta de otra era de la computación, cuando el minimalismo era una necesidad y la brevedad era una virtud. Pero su influencia en el ecosistema Unix es, de hecho, profunda y amplia, y para aprender lo básico hemos tenido la oportunidad de ver cómo el espíritu de ed(1) todavía sobrevive en cientos de herramientas que han heredado su sintaxis, sus comandos, su jerga e idiosincracia. No hemos tenido la oportunidad de hablar sobre todos los comandos disponibles en ed(1) pero en este punto RTFM (lee el maldito manual), eso debería ser suficiente para entender. Dejo algunos puntos de cosas útiles: - No hemos hablado de expresiones regulares, que hacen a ed(1) tan potente. Aprende de ellas. Escribiré un post o dos sobre lo básico pero mientras tanto, ve a aprenderlos. - 'k' es para etiquetar filas, lo que hace la vida más fácil a veces - 'G' es como 'g' pero para editar lineas interactivamente - 'v' es como 'g' pero trabaja en líneas que NO coinciden con un patrón - 'V' es para... (lee los dos puntos anteriores y usa tu cerebro) - 'h' imprime una explicación del último error - 'H' muestra u oculta la explicación de errores - en algunas implementaciones, 'z' provee una función de imprimir por pantalla - en algunas implementaciones, 'y' permite transformar un conjunto de caracteres en otro, más o menos como lo haría tr(1), pero en otras implementaciones copia líneas en un búfer temporal. - en algunas implementaciones, 'x' les permite encriptar un búfer antes de grabarlo en disco, pero en otras se usa para pegar líneas copiadas con 'y'. Te animaría a echarle un vistazo a los siguientes libros y artículos sobre ed(1): - Ed Mastery, by Michael W. Lucas (possibly the best book on ed(1) currently available) https://mwl.io/nonfiction/tools#ed - ed(1) man page in the Single Unix Specification https://pubs.opengroup.org/onlinepubs/9699919799/utilities/ed.html - ed(1) man page from UNIXv1, ca. 1971 http://man.cat-v.org/unix-1st/1/ed - ed(1) man page from UNIXv7, 1979 http://man.cat-v.org/unix_7th/1/ed - ed(1) at gopherpedia gopher://gopherpedia.com/0/Ed (text editor) - A history of UNIX before Berkeley, Ian Darwin & Geoffrey Collyer, section 3.1 http://www.darwinsys.com/history/hist.html - ed(1) is Turing-complete https://nixwindows.wordpress.com/2018/03/13/ed1-is-turing-complete/ - ed(1) is the standard text editor https://www.gnu.org/fun/jokes/ed-msg.txt ¡Eso es todo, amigos! :) -+-+-+-