.. _texto-manipulación: Herramientas de manipulación ============================ Se tratan ahora programas que permiten alterar un flujo de texto y obtener otro modificado\ [#]_. .. _tr: .. index:: tr :manpage:`tr` Permite intercambiar (**tr**\ aducir) en un flujo de texto una serie de caracteres por otros. A diferencia de otros comandos, no admite una argumento que indique un fichero como origen del flujo, por lo que siempre lee de la entrada estándar:: tr [] [] Un ejemplo simple es el siguiente:: $ echo "hola, caracola" | tr 'oa' '04' h0l4, c4r4c0l4 Como se ve sustituye un carácter de la primera cadena por su correspondiente en la segunda. Ahora bien, si se incluye la opción ``-d``, no es necesario indicar la segunda ristra de caracteres, porque lo que hace es, simplemente, eliminar los caracteres indicados:: $ echo "Pedro Sanchez" | tr -d 'aeiou' Pdr Snchz Dado el uso que tiene, esta orden no utiliza expresiones regulares. Ahora bien, sí permite indicar clases de caracteres, tal como se nombraron cuando se dieron las regez *ERE*:: $ echo "pansando a mayusculas" | tr '[:lower:]' '[:upper:]' PASANDO A MAYUSCULAS y también rangos:: $ echo "pansando a mayusculas" | tr 'a-z' 'A-Z' PASANDO A MAYUSCULAS .. warning:: La implementación de :command:`tr` de que disponen los linux, no soporta *utf-8*. Por este motivo, las letras acentuadas no tendrán un buen tratamiento y veremos resoluciones absurdas como esta:: $ echo "ángel" | tr 'á' 'a' aangel .. _sort: .. index:: sort :manpage:`sort` Ordena las líneas del flujo de datos entrante:: sort [] [fichero1 [fichero2...]] Si se especifica un fichero o varios, se tomará como datos entrantes los contenidos de los ficheros. Por ejemplo:: $ sort /etc/passwd Ordena los usuarios locales según el nombre que tengan, puesto que cada línea de este fichero comienza con el nombre de un usuario (véase la :ref:`gestión de usuarios `). La ordenación de las líneas es alfabética. Si se quiere usar una ordenación numérica, puede añadirse la opción ``-n``. Otra opción interesante es ``-k`` que permite indicar el campo\ [#]_ por el que se desea ordenador y ``-t`` que permite indicar el carácter de separación de campos, si éste no son espaciados:: $ sort -nk3 -t: /etc/passwd En este caso, ordenamos por *UID*. .. _uniq: .. index:: uniq :manpage:`uniq` Elimina líneas repetidas del flujo de texto:: uniq [] [ []] Si no se especifica un origen se sobrentiende que este es la entrada estándar. Si no se sobrentiende un destino, se mostrará el resultado en la salida estándar. Para que el comando identifique que las líneas son iguales es necesario que sean consecutivas, por lo que suele usarse en conjunción con :command:`sort` para que este las ordene primero. La siguiente orden muestra las *shells* distintas que usan todos los usuarios definidos en el sistema:: $ getent passwd | egrep -o '[^:]+$' | sort | uniq /bin/bash /bin/false /bin/sync /usr/sbin/nologin .. _paste: .. index:: paste :manpage:`paste` Es una herramienta que permite mezclar varios archivos, de modo que las líneas del archivo resultante están formadas por la yuxtaposición de las líneas correspondientes de los archivos fuente. Por defecto, se usa como caracter de yuxtaposción el tabulador. Un ejemplo ayuda enormemente a entender su utilidad:: $ seq 1 5 > numeros $ echo -e 'a\nb\nc\nd' > letras $ echo -e 'i\nii\niii' > romanos $ paste numeros letras romanos 1 a i 2 b ii 3 c iii 4 d 5 El carácter delimitador puede modificarse con la opción :kbd:`-d`:: $ paste -d, numeros letras romanos 1,a,i 2,b,ii 3,c,iii 4,d, 5,, También puede obtenerse el resultado traspuesto si añadimos la opción :kbd:`-s`:: $ paste -s numeros letras romanos 1 2 3 4 5 a b c d i ii iii Cuando no se especifica fichero alguno, la orden lee de la entrada estándar:: $ seq 1 5 | paste -s -d, 1,2,3,4,5 .. _csplit: .. index:: csplit :manpage:`csplit` Es una orden que permite dividir un archivo de entrada en varios de salida, atendiendo a ciertas líneas que actúan como separador entre sus partes. Por ejemplo, imaginemos ente archivo de entrada: .. code-block:: none Primera parte de este archivo % Segunda parte del archivo de texto % Tercera En este caso, el archivo tiene tres partes separadas por una línea que incluye únicamente el carácter procentual ":kbd:`%`". :command:`csplit` nos permite generar las tres partes por separado:: $ csplit entrada.txt '/^%$/' '{*}' Este es un ejemplo muy simple en que, simplemente, indicamos: * el archivo de entrada * cuál es la línea separadora mediante una expresión regular (de :ref:`tipo BRE `) * generar tantos archivos como partes haya (:kbd:`{*}`), porque de lo contrario la orden se limitará a generar dos archivos separados por el primer separador que encuentre. El resultado es que se generan tres archivos llamados :file:`xx00`, :file:`xx01` y :file:`xx02`. Cada archivo además contendrá la propia línea separadora. Por tanto, :file:`xx02` será\ [#]_: .. code-block:: none % Tercera Hay, no obstante, varias opciones interesantes con la que podemos alterar el comportamiento:: $ csplit -z --suppress-matched -fparte -b%02d.txt caca.txt '/^%$/' '{*}' En este caso, hemos añadido lo siguiente: * Hemos eliminado la generación de archivos vacios. Esto tiene mucha utilidad si la primera línea del archivo de entrada es una línea separadora, porque eso supondría que :file:`xx00` resulta un archivo vacío. * Hemos eliminado la línea separadora de los archivos de salida, lo cual dependiendo del caso puede interesarnos o no. * Hemos modificado los nombres de los archivos de salida (con :kbd:`-f` y :kbd:`-b` con lo que ahora los archvios de salida se llamarán :file:`parte00.txt`, :file:`parte01.txt`, etc. .. _cut: .. index:: cut :manpage:`cut` Como su nombre indica, :command:`cut` permite seleccionar una parte de cada una de las líneas que componen un fichero. Qué criterio se seguirá para seleccionar qué interesa dependerá de qué opciones se le pasen. La sintaxis general es:: $ cut [] [fichero1 [fichero2...]] Como en otros casos, no expresar fichero alguno supondrá que el flujo se obtendrá de la entrada estándar. Si lo que se pretende es seleccionar por posición, debemos usar la opción ``-c``\ [#]_. Por ejemplo:: $ echo "1234567890" | cut -c3-5 345 Para expresar el rango, puede usarse como se ha hecho (*Inicio-Fin*), pero tambíen indicando sólo el principio (*Inicio-*):: $ echo "1234567890" | cut -c5- 567890 o indicando sólo el final (*-Final*):: $ echo "1234567890" | cut -c-5 12345 Si expresamos sólo un número, entonces escogeremos exactamente ese carácter:: $ echo "1234567890" | cut -c5 5 También es posible seleccionar distintos rangos, separándolos por comas:: $ echo "1234567890" | cut -c1-2,4,6-8 124678 Si en cambio nos encontramos con líneas constituidas por campos a los que separa un carácter delimitador, entonces la selección es mejor hacerla a través de la opción ``-d`` que nos permite indicar el delimitador y ``-f`` que nos pormite indicar qué campos queremos seleccionar. Si no se indica delimitador, se sobrentiende que es la tabulación. Para expresar qué cambios se usa exactamente la misma sintaxis que con la opción ``-c``:: $ echo "campo1,campo2,campo3" | cut -d, -f2 campo2 Un flujo que se presta enormemente al uso de :command:`cut` con este criterio es el de definición de los usuarios o los grupos, cuyos campos están separados por dos puntos:: $ getent group | cut -d: -f1 [... Nombres de todos los grupos que existen en el sistema ...] Cuando se usa este último criterio de selección, es conveniente saber que añadir la opción ``-s`` excluye las líneas que no contienen el delimitador. .. _sed: .. index:: sed :manpage:`sed` :command:`sed` es el acrónimo de *Stream EDitor* (o sea, *editor de flujo*) y es precisamente eso: un programa que recibe un flujo de caracteres y es capaz de alterarlo a través de las transformaciones que ordenemos. La sintaxis es la siguiente:: $ sed [opciones] [fichero] Básicamente, se proporciona una fuente de datos, esto es, un fichero o la entrada estándar si no se especifica ninguno, y una cadena que define qué transformación se quiere hacer. Es posible también añadir algunas opciones que modifican la forma en que :command:`sed` se comporta. En cualquier caso la que usaremos siempre es ``-r`` porque permite usar *regex* de tipo *ERE*, en vez de *BRE*. Es un programa que permite hacer transformaciones bastante complicadas y cuyo manejo absoluto requiere bastante más que una pequeña reseña en unos apuntes. Así que nos limitaremos a realizar modificaciones sencillas. Lo que es imprescindible en cualquier caso es entender bien como funciona: :command:`sed` recibe el flujo de caracteres línea a línea y para cada línea aplica la transformación o las transformaciones que se le hayan ordenado; y así transformada es devuelta o no. Así continúa operando hasta llegar a la última de las líneas\ [#]_. Empecemos por lo más sencillo:: $ sed -r '' /etc/oasswd [... Contenido del fichero /etc/passwd ...] Como la transformación es no hacer nada, :command:`sed` no realiza ninguna transformación, y devuelve exactamente las mismas líneas que recibió. La consecuencia es que veremos en la pantalla el fichero original sin retocar. Esto es así, porque de modo predeterminado :command:`sed` imprime la transformada. Sin embargo, si añadimos la opción ``-n``, pasará a no imprimir a menos que explícitamente así que diga:: $ sed -nr '' /etc/passwd No obtendremos absolutamente nada. Como podemos incluir en nuestra transformación ``p``, que significa imprimir, podría haber escrito:: $ sed -nr 'p' /etc/passwd [... Contenido del fichero /etc/passwd ...] Y habríamos obtenido el mismo efecto que con la primera orden. Una de las transformaciones más socorridas es modificar la línea del siguiente modo:: s:::[modificadores] Por ejemplo:: $ sed -r 's:^:L.-:' /etc/passwd L.-root:x:0:0:root:/root:/bin/bash [... Resto del fichero modificado ...] Añadirá al principio de línea los caracteres :kbd:`L.-`. Las transformaciones pueden ser todo lo complicadas que nos permitan las expresiones regulares. Por ejemplo:: $ sed -r 's:(\w+):#\1#:' /etc/passwd #root#:x:0:0:root:/root:/bin/bash [... Resto del fichero modificado ...] Como vemos hemos logrado rodear la primera palabra por dos almohadillas. Esto se debe a que la transformación consiste en capturar la palabra y sustituirla por una almohadilla, seguida por la propia palabra y otra almohadilla. Sin embargo no se han modificado el resto de palabras, esto es debido a que la sustitución acaba cuando se logra hacer una vez. Si queremos que la sustitución se repita a lo largo de toda la línea puede añadirse el modificador ``g``:: $ sed -r 's:(\w+):#\1#:g' /etc/passwd #root#:#x#:#0#:#0#:#root#:/#root#:/#bin#/#bash# [... Resto del fichero modificado ...] Es posible realizar dos transformaciones sobre las líneas, separándolas por un punto y coma:: $ sed -r 's:(\w+):#\1#: ; s:^:L.-:' /etc/passwd L.-#root#:x:0:0:root:/root:/bin/bash [... Resto del fichero modificado ...] Sustituir no es la única acción que puede hacer :command:`sed`. También puede borrar por completo la línea:: $ sed -r 'd' /etc/passwd Que no imprimirá nada, obviamente, puesto que antes de devolver la línea la borramos. Esto resulta aparentemente inútil, pero no lo es, porque :command:`sed` también permite indicar sobre qué líneas queremos realizar la transformación\ [#]_:: $ sed -r '1d' /etc/oasswd [... Todo el fichero excepto la primera línea ...] $ sed -r '$d' /etc/passwd [... Todo el fichero excepto la última línea ...] $ sed -r '1,5d' /etc/passwd [... Todo el fichero excepto las cinco primeras líneas ...] Cuando lo que se quiere es realizar la acción sobre todas las líneas menos sobre las que se especifica, se añade una exclamación:: $ sed -r '1!d' /etc/oasswd [... Sólo la primera línea ...] $ sed -r '$d' /etc/passwd [... Sólo la última línea ...] $ sed -r '1,5!d' /etc/passwd [... Sólo las primeras cinco primeras líneas ...] Para seleccionar líneas también podemos usar expresiones regulares:: $ sed -r '/^u/d' /etc/passwd [... No se muestran las líneas que empiezan por u ...] $ sed -r '2,/^u/d' /etc/passwd [... No se muestra desde la segunda líneas hasta la primera que empieza por u ...] Si se quiere realizar más de una transformación sobre una selección de líneas, pueden usarse paréntesis:: $ sed -r '1,5{s:^:P: ; s:$:F:}' /etc/passwd [ ... Sólo entre las líneas 1 y 5 se realizan las sustituciones ...] Por último, otra acción que puede realizar es parar la edición antes de que acabe el flujo. Para ello existe ``q`` y ``Q``. La diferencia entre una y otra es que ``Q`` acaba sin imprimir la línea pendiente:: $ sed -r '5q' /etc/passwd [... Se imprimen las cinco primeras líneas ...] $ sed -r '5Q' /etc/passwd [... Se imprimen las cuatro primeras líneas ...] .. _awk: .. index:: awk :manpage:`awk`\ [#]_ Al igual que :command:`sed` es un programa, cuyas capacidades exceden el propósito de estos apuntes. De hecho, es un `lenguaje de programación completo `_. Nosotros, en cambio, le daremos un uso bastante limitado: lo utilizaremos para tratar ficheros de texto cuyas líneas estén constituidas por campos y en las que queramos realizar cambios. :command:`cut` con su opción ``-f`` sirve para esto mismo, pero no nos permite hacer transformaciones en la línea. La sintaxis más básica de :command:`awk` es la siguiente:: awk [] [fichero] Antes de continuar es pertinente advertir de que *debian* permite instalar dos variantes de :command:`awk`: :command:`mawk`, que es la que viene instalada por defecto, y es menos potente aunque más ligera; y :command:`gawk`. La primera, al igual que pasa con :command:`tr` no soporta *utf-8*. :command:`awk` opera igual que :command:`sed`: lee línea a línea y dentro de cada una de ellas identifica los campos que existen. El delimitador es cualquier carácter de espaciado (el propio espacio o la tabulación), pero a diferencia de :command:`cut`, los colapsa, de modo que si hay varios espacios o tabuladores seguidos los considera como uno sólo. Con respecto a esto define varias variables: ``NR`` Es el número de registro, es decir el número de línea. ``NF`` Es el número total de campos que tiene una línea. ``$0`` Almacena el contenido de una línea. ``$1``, ``$2``, ... , etc. Almacena el contenido de cada campo. Además, podemos cambiar el delimitador añadiendo la opción ``-F``. Así, por ejemplo, para obtener los nombres de los grupos definidos en el sistema puede hacerse lo siguiente:: $ getent group | awk -F: '{print $1}' La acción que realicemos debe escribirse entre llaves y en caso de querer hacer varias, todas estarán dentro de ellas y se separarán por un punto y coma. En particular, hemos hecho :kbd:`print $1` para imprimir (``print``) el primer campo. Esta acción se puee usar de forma que juxtapongamos varios argumentos:: $ getent group | awk -F: '{print NR ": " $1}' 1: root 2: daemon [... Resto de grupos del sistema ...] También es posible separar los argumentos con comas. En este caso, :command:`awk` interpondrá entre un argumento y otro el carácter que tenga definido como separador de campos para la salida, que no tiene que coincidir con el separador de campos para la entrada. El predeterminado es el espacio de ahí que:: $ getent group | awk -F: '{print NR, $1}' 1 root 2 daemon [... Resto de grupos del sistema ...] separa el número de registro de del primer campo con un espacio, a pesar de que el caracter delimitador de entrada son los dos puntos. Tal delimitador de salida se almacena en el valor de la variable :var:`OFS`, de modo que, si cambiamos su valor, cambiará la salida:: $ getent group | awk -F: -v OFS=".- " '{print NR, $1}' 1.- root 2.- daemon [... Resto de grupos del sistema ...] :command:`awk` dispone de bastantes funciones que permiten alterar los valores de las variables que se muestran, algunas de las cuales permiten hacer sustituciones como las vistas con :command:`sed`. Basta consultar un buen manual para conocerlas. Para ilustrar esto, podemos usar ``toupper`` que pasa a mayúsculas:: $ getent group | awk -F: '{print NR, toupper($1) }' 1 ROOT 2 DAEMON [... etc ...] Para culminar la ínfima aproximación a :command:`awk`, debe también explicarse cómo filtrar líneas. Esto se logra anteponiendo una expresión regular rodeada por barras:: $ getent group | awk -F: '/^[^r]/ {print NR, $1 }' 2 daemon 3 bin [... etc ...] En este caso la expresión regular selecciona líneas que no comiencen con la letras «*r*», de ahí que la primera línea no aparezca como resultado. Escrito de esta forma, la expresión regular se aplica a toda la línea, esto es, al valor de la variable :var:`$0`. Si se quiere filtrar sólo por el valor de un campo, puede usarse la siguiente sintaxis:: $ getent group | awk -F: '$4 ~ /./ {print NR, $0 }' 5 adm:x:4:syslog,usuario 18 cdrom:x:24:usuario 21 sudo:x:27:usuario 23 dip:x:30:usuario 35 plugdev:x:46:usuario 47 lpadmin:x:108:usuario 58 sambashare:x:118:usuario En este caso, sólo hemos mostrado las líneas que contienen algo en el cuerto campo. Como este, además, es el último, podríamos haberlo resuelto también así:: $ getent group | awk -F: '$NF ~ /./ {print NR, $0 }' [... Obtenemos la misma salida ...] por cuanto :var:`NF` vale *4* y :var:`$NF` equivale a :var:`$4`. .. note:: Para una información más sistemática consulte :ref:`awk en una línea `. .. rubric:: ¿Qué herramiento uso? .. todo:: Aquí convendría establecer las pautas para el uso de :ref:`sed `, :ref:`cut `, :ref:`grep ` y :ref:`awk `. .. rubric:: Notas al pie .. [#] En puridad, ya :ref:`grep ` y :ref:`wc ` permiten esto: el primero filtra líneas, por lo que se obtiene un flujo distinto y el segundo convierte el flujo en una pequeña estadística sobre el mismo. .. [#] También existe la opción ``-b`` que selecciona por posición, pero en vez de identificar *caracteres* identifica *bytes*. Recuérdese que hay sistemas de codificación (y *UTF-8* está entre ellos) en que cada carácter no ocupa un *byte* (repásese el ejemplo dado al explicar :ref:`tr `). .. [#] :file:`xx00` no tiene línea separadora por lo que no contendrá tal línea. .. [#] En realidad, ``-k`` escoger algo más complejo que un simple campo (consulte :manpage:`sort(1)`). Por lo pronto permite escoger hasta dos campos como criterio:: $ sort -nk4,3 /etc/passwd .. [#] Como ya se ha dicho :command:`sed` es bastante complejo. Este es su comportamiento, pero permite leer la siguiente línea antes de haber acabado de transformar la presente, o almacenar líneas transformadas y dejarlas reservadas para devolverlas más tarde. No trataremos estas capacidades. .. [#] Para notar la última línea se usa el dólar. Ahora bien, aunque es posible saber que se está en la última línea, es imposible conocer que es está en la penúltima, es decir, expresiones como :kbd:`$-1` no son válidas. Esto es debido al modo en que actúa el programa: al ir leyendo línea a línea, es incapaz de saber las líneas que aún le quedan por leer. .. [#] La versión de :command:`awk` instalada en *Debian* es :command:`mawk`. Existe otra más potente pero más lenta, con el nombre de :manpage:`gawk`