Programación del Shell Linux

Aquí tenemos un libro libre y completo sobre Shell

La sed del "conocimiento libre" es muy bienvenida.

Comandos Shell Script

Diagrama Sintáctico
Júlio Neves
Júlio Neves
Home | Artículos Português InglesAgradecimientos | Links Amigos

Comprar el libro

Changelogs

  Conversación de Bar 1  

  Conversación de Bar 2  

  Conversación de Bar 3  

  Conversación de Bar 4  

  Conversación de Bar 5  

  Conversación de Bar 6  

  Conversación de Bar 7  

  Conversación de Bar 8  

  Conversación de Bar 9  

  Conversación de Bar 10  

  Conversación de Bar 11  

  Aperitivo  

Acompañamientos para el aperitivo


En construcción para siempre!
Esta página, a pesar de estar dentro del contenido de Conversaciones de Bar, nunca fue publicada en la Linux Magazine. Trata de artículos que escribí para otras publicaciones, ayudas útiles que leí navegando por la internet (y en este caso con los debidos créditos), contribuciones de estas personas maravillosas y siempre dispuestas a ayudar del Software Libre, y de la imprescindible "Lista de Shell Script"

Pasando parámetros con xargs

Existe un comando, cuya función principal es construir listas de parámetros y pasarlas para la ejecución de otros programas o instrucciones. Este comando es el xargs y debe ser usado de la siguiente manera:

    xargs [comando [argumento inicial]]

En el caso de que el comando (que puede ser inclusive un script Shell), sea omitido, será usado por default el echo.

El xargs combina el argumento inicial con los argumentos recibidos de la entrada patrón, de forma de ejecutar el comando especificado una o más veces.

Ejemplo:

Vamos a buscar una cadena de caracteres en todos los archivos dentro de un determinado directorio, usaremos el comando find con la opción -type f para buscar solamente los archivos normales, despreciando directorios, archivos especiales, archivos de uniones, etc, y vamos a hacer la busqueda lo más general posible, recibiendo el nombre del directorio inicial y la cadena a ser buscada como parámetros. Para eso hacemos:

$ cat grepr # # Grep recursivo # Busca la cadena de caracteres definida en $2 a partir del directorio $1 # find $1 -type f -print|xargs grep -l "$2"

Ejecutando este script buscamos, a partir del directorio definido en la variable $1, todos los archivos que contengan la cadena definida en la variable $2.

Exactamente lo mismo podría hacerse si la línea del programa fuera la siguiente:

    find $1 -type f -exec grep -l "$2" {} \;

El primer proceso tiene dos grandes desventajas sobre el anterior:

  • La primera es bastante visible: el tiempo de ejecución de este método es muy superior al segundo, eso porque el grep será hecho en cada archivo que le sea pasado por el find, uno a uno, al paso que con el xargs, será pasada toda, o en la peor de las hipótesis, la mayor parte posible, de la lista de archivos generada por el find;

  • Dependiendo de la cantidad de archivos encontrados que atiendan al find, podemos recibir aquel famoso y fatídico mensaje de error Too many arguments indicando una sobrecarga en la pila de ejecución del grep. Como fue dicho en el ítem anterior, si usamos el xargs, éste pasará hacia el grep la mayor cantidad de parámetros posible, suficiente para no causar este error y en caso necesario, ejecutará el grep más de una vez.
Pinguim com placa de atenção (em espanhol) Atención aquellas personas de linux que usan el ls colorido como estándar: en los ejemplos siguientes que incluyen esta instrucción, deben usar la opción --color=none, en caso contrario, existen grandes posibilidades de que los resultados no sean los esperados (;-).

Vamos ahora a analizar un ejemplo que es más o menos lo contrario de este que acabamos de ver. Esta vez, vamos a hacer un script para borrar todos los archivos del directorio actual y que pertenezcan a un determinado usuario.

La primera idea que surge es, como en el caso anterior, usar un comando find, de la siguiente manera:

    find . -user cara -exec rm -f {} \;

Casi estaría correcto, el problema es que de esta forma se estarían borrando no solamente los archivos de cara en el directorio actual, sino también de todos los otros sub-directorios "colgados" a éste. Veamos entonces como hacerlo correctamente:

    ls -l | grep " cara " | cut -c55- | xargs rm

De esta forma, el grep seleccionó los archivos que contenían la cadena cara en el directorio actual listado por el ls -l. El comando cut tomo solamente el nombre de los archivos, pasándolos hacia el borrado, a cargo del rm usando el comando xargs como puente

El xargs es también una excelente herramienta de creación de one-liners (scripts de solamente una línea). Mira éste para listar todos los propietarios de archivos (inclusive sus links) "colgados" en el directorio /bin y sus sub-directorios.

$ find /bin -type f -follow | \ xargs ls -al | tr -s ' ' | cut -f3 -d' ' | sort -u

Muchas veces el /bin es un link (si no estoy equivocado, en Solaris es así) y la opción -follows obliga al find a seguir el link. El comando xargs alimenta el ls -al y la secuencia de comandos siguiente es para tomar solamente el 3er campo (propietario) y clasificarlo devolviendo solamente una vez cada propietario (opción -u del comando sort, que equivale al comando uniq).

Opciones del xargs

Las opciones del xargs pueden se usadas para construir comandos extremamente poderosos.

Opción -i

Para ejemplificar esto y comenzar a entender las principales opciones de esta instrucción, vamos a suponer que tenemos que borrar todos los archivos con extensión .txt en el directorio actual y presentar sus nombres en pantalla. Veamos que podemos hacer:

$ find . -type f -name "*.txt" | \ xargs -i bash -c "echo borrando {}; rm {}"

La opción -i del xargs cambia pares de llaves ({}) por la cadena que está recibiendo a través del pipe (|). Entonces en este caso las llaves ({}) serán cambiadas por los nombres de los archivos que satisfagan al comando find.

Opción -n

Veamos este pequeño juego que vamos a hacer con el xargs:

$ ls | xargs echo > arch.ls $ cat arch.ls arch.ls arch1 arch2 arch3 $ cat arch.ls | xargs -n1 arch.ls arch1 arch2 arch3

Cuando mandamos la salida del ls hacia el archivo usando el xargs comprobamos lo que ya dijimos, o sea, el xargs manda todo lo que sea posible (lo suficiente para no generar una sobrecarga en la pila) de una vez sola. En seguida, usamos la opción -n 1 para listar uno por uno. Sólo para estar seguros, mira el ejemplo siguiente, donde listaremos dos archivos en cada línea:

$ cat arch.ls | xargs -n 2 arch.ls arch1 arch2 arch3

Sin embargo, la línea de arriba podría (y debería) ser escrita sin usar el pipe (|), de la siguiente forma:

$ xargs -n 2 < arch.ls

Opción -p

Otra excelente opción del xargs es -p, en la cual el sistema pregunta si tu realmente deseas ejecutar el comando. Digamos que en un directorio tengas archivos con la extensión .bug y .ok, los .bug tienen problemas que después de corregidos son grabados como .ok. Echa una mirada a la lista de este directorio:

$ ls dir arch1.bug arch1.ok arch2.bug arch2.ok ... arch9.bug arch9.ok

Para comparar los archivos buenos con los defectuosos, hacemos:

$ ls | xargs -p -n2 diff -c diff -c arch1.bug arch1.ok ?...y .... diff -c arch9.bug arch9.ok ?...y

Opción -t

Para finalizar, el xargs también tenemos la opción -t, donde va mostrando las instrucciones que montó antes de ejecutarlas. Me gusta mucho esta opción para ayudar a depurar el comando que fue montado.

Resumen

Entonces podemos resumir el comando de acuerdo con la siguiente tabla:

-t   Muestra la línea de comando montada antes de ejecutarla  
  Opción     Acción
-i   Substituye el par de llaves ({}) por las cadenas recibidas  
-nNum   Manda el máximo de parámetros recibidos, hasta el máximo de Num para que el comando sea ejecutado  
-lNum   Manda el máximo de líneas recibidas, hasta el máximo de Num para que el comando sea ejecutado  
-p   Muestra la línea de comando montada y pregunta si desea ejecutarla  

Here Strings

Primero un programador con complejo de inferioridad creó el redireccionamiento de entrada y lo representó con un signo de menor (<) para representar sus sentimientos. En seguida, otro sintiéndose todavía peor, creó el here document representándolo por dos signos de menor (<<) porque su complejo era mayor. El tercero, pensó: "estos dos no saben lo que es estar deprimido"... Entonces creó el here strings representándolo por tres signos de menor (<<<).

Bromas a parte, el here strings es utilísimo y, no sé porque, es un perfecto desconocido. En la poquísima literatura que hay sobre el tema, se nota que el here strings es frecuentemente citado como una variante del here document, teoría con la que discrepo pues su aplicabilidad es totalmente diferente de aquella. Su sintaxis es simple:

    $ comando <<< $cadena

Donde cadena es expandida y alimenta la entrada primaria (stdin) de comando.

Como siempre, vamos directo a los ejemplos de los dos usos más comunes para que vosotros mismos saquéis las conclusiones.

  • Uso #1. Substituyendo la tan usada construcción echo "cadeia" | comando, que obliga a un fork, creando un subshell y aumentando el tiempo de ejecución.

Ejemplos:

$ a="1 2 3" $ cut -f 2 -d ' ' <<< $a # Normalmente se hace: echo $a | cut -f 2 -d ' ' 2 $ echo $Nomearch Mis Documentos # Arrrghhh! $ tr "A-Z " "a-z_" <<< $Nomearch # Substituyendo el echo $Nomearch | tr "A-Z " "a-z_" mis_documentos $ bc <<<"3 * 2" 6 $ bc <<<"scale = 4; 22 / 7" 3.1428

Para mostrar la mejoría en el desempeño, vamos a hacer un loop de 500 veces usando el ejemplo dado para el comando tr: Veamos ahora esta secuencia de comandos con medidas de tiempo:

$ time for ((i=1; i<= 500; i++)); { tr "A-Z " "a-z_" <<< $Nomearch >/dev/null; } real 0m3.508s user 0m2.400s sys 0m1.012s $ time for ((i=1; i<= 500; i++)); { echo $Nomearch | tr "A-Z " "a-z_" >/dev/null; } real 0m4.144s user 0m2.684s sys 0m1.392s

Veamos ahora esta otra secuencia de comandos con medidas de tiempo:

$ time for ((i=1;i<=100;i++)); { who | cat > /dev/null; } real 0m1.435s user 0m1.000s sys 0m0.380s $ time for ((i=1;i<=100;i++)); { cat <(who) > /dev/null; } real 0m1.552s user 0m1.052s sys 0m0.448s $ time for ((i=1;i<=100;i++)); { cat <<< $(who) > /dev/null; } real 0m1.514s user 0m1.056s sys 0m0.412s

Observando este cuadro verás que en el primero, usamos la forma convencional, en el segundo usamos un named pipe temporal para ejecutar una substitución de procesos y en el tercero usamos here strings. Notaras también que al contrario del ejemplo anterior, aqui el uso de here strings no fue lo más veloz. Pero observad también que en este último caso el comando who está siendo ejecutado en un subshell y eso aumentó el proceso como un todo.

Veamos una forma rápida de insertar una línea como encabezamiento de un archivo:

$ cat num 1 2 3 4 5 6 7 8 9 10 $ cat - num <<< "Impares Pares" Impares Pares 1 2 3 4 5 6 7 8 9 10

  • Uso #2. Otra buena forma de usar el here strings es juntándolo con un comando read, no perdiendo de vista lo que aprendimos sobre IFS (vedlo aquí, en la explicación del comando for). El comando cat con las opciones -vet muestra el <ENTER> como $, el <TAB> como ^I y los otros caracteres de control con la notación ^L donde L es una letra cualquiera. Veamos entonces el contenido de una variable y después vamos a leer cada uno de sus campos:

Ejemplos:

$ echo "$línea" Leonardo Mello (21)3313-1329 $ cat -vet <<< "$línea" Leonardo Mello^I(21)3313-1329$ # Los separadores son blanco y <TAB> (^I) $ read Nom SNom Tel <<< "$línea" $ echo "${Nom}_$S{Nom}_$Tel" # Vamos a ver si leyó cada uno de los campos Leonardo_Mello_(21)3313-1329 # Leyó porque los separadores eran iguales al IFS

También podemos leer directamente de un vector (array) vedlo:

$ echo $Frutas Pera:Uva:Manzana $ IFS=: $ echo $Frutas Pera Uva Manzana # Sin las comillas el shell muestra el IFS como blanco $ echo "$Frutas" Pera:Uva:Manzana # Ahhh, ahora sí! $ read -a aFrutas <<< "$Frutas" # La opción -a del read, lee un vector $ for i in 0 1 2 > do > echo ${aFrutas[$i]} # Imprimiendo cada elemento del vetor > done Pera Uva Manzana

Rotatório de Tiago

Estaba, como lo hago todos los días dandole un repaso a los e-mails de la "Lista de Shell Script" , cuando descubro algo totalmente inusitado de Tiago Barcellos Peczenyj.

Cuando resolví crear este conjunto de consejos, me acordé de eso y le pedí para que me enviara aquel e-mail nuevamente. El texto siguiente es el e-mail que me mandó, solo inserté el último ejemplo y saqué las abrebiaturas.

Julio descubrí una forma en la que el Shell crea combinaciones haciendo rotación con los elementos estipulados. Podemos generar todos los binarios de 0000 a 1111 de la siguiente forma:

$ A={0,1} $ eval echo $A$A$A$A 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111

Una aplicación práctica que veo es para combinar valores diferentes sin tener que encadenar loops ni usar el =seq

$ A={`seq -s , -f "_%g" 3`} $ eval echo -e $A$A$A |tr ' _' '\n ' | grep -vE '.+?(\b[0-9]+\b).+?\1' 1 2 3 1 3 2 2 1 3 2 3 1 3 1 2 3 2 1

En este caso combiné los números de 1 a 3 eliminando repeticiones con el grep. usé un tr foca'podre' para tratar mejor los datos, saltando línea. El grep es simple como puedes notar, compruebo si una determinada parte de la combinación (.+?(\b[0-9]+\b).+?) existe en otra parte (\1), si existe, no la dejo imprimir por causa de la opción -v, y así

    1 1 2
    1 2 1
    1 1 1
no son impresos.

Ahora va mi ejemplo: el one-liner siguiente genererá todos los permisos posibles (en octal) para el archivo arq (el ejemplo fué interrumpido por que existen 512 combinaciones de permisos posibles).

$ A={`seq -s , 0 7`} $ eval echo -e $A$A$A | tr ' ' '\n' | xargs -i bash -c "chmod {} arq; ls -l arq" ---------- 1 julio julio 100 2006-11-27 11:50 arq ---------x 1 julio julio 100 2006-11-27 11:50 arq --------w- 1 julio julio 100 2006-11-27 11:50 arq --------wx 1 julio julio 100 2006-11-27 11:50 arq -------r-- 1 julio julio 100 2006-11-27 11:50 arq -------r-x 1 julio julio 100 2006-11-27 11:50 arq -------rw- 1 julio julio 100 2006-11-27 11:50 arq -------rwx 1 julio julio 100 2006-11-27 11:50 arq . . . . . . . . . . . . . . . . . . -rwxrwxrw- 1 julio julio 100 2006-11-27 11:50 arq -rwxrwxrwx 1 julio julio 100 2006-11-27 11:50 arq

Veamos este ejemplo paso a paso para entenderlo:

$ echo $A {0,1,2,3,4,5,6,7} $ eval echo -e $A$A$A 000 001 002 003 004 005 006 007 010 ... ... 767 770 771 772 773 774 775 776 777 $ eval echo -e $A$A$A | tr ' ' '\n' # El tr cambiará cada espacio en blanco por um <ENTER> 000 001 002 003 . . . 774 775 776 777

A continuación el xargs (clique para consejos sobre el xargs) ejecuta el comando bash -c (que sirve para ejecutar una línea de comandos) que por cada vez que se ejecuta chmod y el ls -l permite mostrar que los permisos están siendo alterados.

Aritmética en Shell

Antiguamente usábamos el comando expr para hacer operaciones aritméticas y mucha gente aun la usa, pues es compatible con cualquier ambiente.

Ejemplo:

$ expr 7 \* 5 / 3 # 7 veces 5 = 35 dividido por 3 = 11 14

En este articulo podremos ver otras formas no tan conocidas, sin embargo más simple de usar, más elaboradas y con mayor precisión.

El uso de bc

Una forma fantástica de hacer cálculos en Shell – usada normalmente cuando la expresión aritmética es más compleja, o cuando es necesario trabajar con cifras decimales – es usar la instrucción de calculo del UNIX/LINUX. El bc. Mira como:

Ejemplo:

$ echo "(2 + 3) * 5" | bc # Paréntesis usados para dar preferencia 25

Para trabajar con números reales (números no necesariamente enteros), especifique la precisión (cantidad de decimales) con la opción scale del comando bc. Así veamos el penúltimo ejemplo:

$ echo "scale=2; 7*5/3" | bc 11.66

Otros ejemplos:

$ echo "scale=3; 33.333*3" | bc 99.999 $ num=5 $ echo "scale=2; ((3 + 2) * $num + 4) / 3" | bc 9.66

Obviamente todos los ejemplos de arriba en el caso de linux, podrían (y deberían) ser escritos usando Here Strings. Veamos los últimos como quedarían:

$ bc <<< "scale=3; 33.333*3" 99.999 $ num=5 $ bc <<< "scale=2; ((3 + 2) * $num + 4) / 3" 9.66

Una vez apareció en la lista (excelente a propósito) de Shell script en Yahoo ( "Lista de Shell Script" ) un persona con la siguiente duda: "yo tengo un archivo cuyos campos están separados por y el tercero de ellos posee números. Como puedo calcular la suma de todos los números de esta columna del archivo?"

Yo envié la respuesta siguiente:

$ echo $(cut -f3 num | tr '\n' +)0 | bc 20.1

Vamos por partes para entenderlo mejor y primero vamos ver como era el archivo que hice para comprobarlo:

$ cat num a b 3.2 a z 4.5 w e 9.6 q w 2.8

Como se puede ver, está dentro del patrón del problema, donde yo tengo como tercer campo números reales. A ver lo que haría la primera parte de la línea de comandos, donde yo transformo los caracteres (new-line) en un señal de más (+):

$ cut -f3 num | tr '\n' + 3.2+4.5+9.6+2.8+

Si yo mandase de esa manera hacia el bc, él me devolvería un error por causa de aquel signo de más (+) suelto al final del texto. Mi solución fue poner un cero al final, pues sumando cero el resultado no se alterará. Veamos entonces como quedó:

$ echo $(cut -f3 num | tr -s '\n' +)0 3.2+4.5+9.6+2.8+0

Eso es lo que se acostumbra llamar one-liner, esto es, códigos que serían complicados en otros lenguajes (normalmente sería necesario crear contadores y hacer uno loop de lectura sumando el tercer campo al contador) y en Shell son escritos en una única línea.

Hay también gente que llama eso de método KISS, que es el acrónimo de Keep It Simple Stupid. smile

Pero el uso potencial de esta calculadora no se acaba ahí, existen diversas facilidades proporcionadas por ella. Veamos sólo este ejemplo:

$ echo "obase=16; 11579594" | bc B0B0CA $ echo "ibase=16; B0B0CA " | bc # B, zero, B, zero, C, e A 11579594

En estos ejemplos vimos como hacer cambios de base de numeración con el uso del bc. En la primera ponemos la base de salida (obase) como 16 (hexadecimal) y en la segunda, dijimos que la base de la entrada (ibase) era 10 (decimal).

Otras formas de trabajar con enteros

Otra forma mucho mejor de hacer cálculos es usar la notación $((exp aritmética)). Es bueno estan atento, sin embargo, al hecho de que esta sintaxis no es universal. Bourne Shell (sh), por ejemplo, no la reconoce.

Ejemplo:

Usando el mismo ejemplo que ya habíamos usado:

$ echo $(((2+3)*5)) # Los paréntesis mas internos priorizaran o 2+3 25

Fíjate ahora en esta locura:

$ tres=3 $ echo $(((2+tres)*5)) # Variable tres no precedida por $ 25 $ echo $(((2+$tres)*5)) # Variable tres precedida por $ 25

Ei!! No es el signo ($) que la precede lo que caracteriza una variable? Si, pero e todos los sabores UNIX que probé, solo bash o ksh, las dos formas producen una buena aritmética.

Presta atención a esta secuencia:

$ unset i # $i mooorreu! $ echo $((i++)) 0 $ echo $i 1 $ echo $((++i)) 2 $ echo $i 2

Fíjate que a pesar que la variable no esta definida, pues fue hecho uno unset en ella, ninguno de los comandos dió error, porque, como estamos usando construcciones aritméticas, siempre que una variable no existe, es inicializada con cero (0).

Fíjate que el i++ produjo cero (0). Esto ocurre porque este tipo de construcción se llama pos-incremento, esto es, primeramente el comando es ejecutado y sólo entonces la variable es incrementada. En el caso del ++i, fue hecha un pre-incremento: primero incrementó y solo despues el comando fue ejecutado.

También son válidos:

$ echo $((i+=3)) 5 $ echo $i 5 $ echo $((i*=3)) 15 $ echo $i 15 $ echo $((i%=2)) 1 $ echo $i 1

Estas tres operaciones serian lo mismo que:

    i=$((i+3))
    i=$((i*3))
    i=$((i%2))

Y esto seria válido para todos los operadores aritméticos, lo que en resumen produciría la siguiente tabla:

Expansión Aritmética
||   Or lógico
  Expresión     Resultado  
id++ id--   pós-incremento y pós-decremento de variables
++id -–id   pré-incremento y pré-decremento de variables
**   exponenciación
* / %   multiplicación, división, resto de la división
+ -   adición, sustracción
<= >= < >   comparación
== !=   igualdad, desigualdad
&&   And lógico

Pero el máximo exponente de esta forma de construcción con doble paréntesis es la siguiente:

$ echo $var 50 $ var=$((var>40 ? var-40 : var+40)) $ echo $var 10 $ var=$((var>40 ? var-40 : var+40)) $ echo $var 50

Este tipo de construcción debe ser usado de la siguiente forma: en caso que la variable var sea mayor que 40 (var>40), entonces (?) hace var igual a var menos 40 (var-40), si no (:) hacer var igual a var más 40 (var+40). Lo que quiero decir es que los caracteres punto-de-interrogación (?) y dos-puntos (:) hacen el papel de "entonces" y "si no", sirviendo así para montar una operación aritmética condicional.

Al igual que usamos la expresión $((...)) para hacer operaciones aritméticas, también podríamos usar la intrínseca (built-in) let o construcciónes del tipo $[...].

Los operadores son los mismos para estas tres formas de construcción, lo que varía un poco es la operación aritmética condicional con el uso del let. Veamos como sería:

$ echo $var 50 $ let var='var>40 ? var-40 : var+40' $ echo $var 10 $ let var='var>40 ? var-40 : var+40' $ echo $var 50

Baseando

Si quieres trabajar con bases diferentes de la decimal, basta con usar el formato:

    base#numero

Donde base es un número decimal entre 2 y 64 en nombre del sistema de numeración, y numero es un número en el sistema numérico definido por base. Si base# fuese omitida, entonces 10 se asume por defecto default. Los guarismos mayores que 9 son representados por letras minúsculas, mayúsculas, @ y _, en esta orden.

Si base fuese menor o igual a 36, mayúsculas o minúsculas pueden ser usadas indiferentemente para definir guarismos mayores que 9 (no está mal escrito, los guarismos del sistema hexadecimal, por ejemplo, varían entre 0 (cero) y F). Veamos como funciona:

$ echo $[2#11] 3 $ echo $((16#a)) 10 $ echo $((16#A)) 10 $ echo $((2#11 + 16#a)) 13 $ echo $[64#a] 10 $ echo $[64#A] 36 $ echo $((64#@)) 62 $ echo $((64#_)) 63

En estos ejemplos usé las notaciones $((...)) y $[...] indistintamente, para demostrar que ambas funcionan.

Funciona también un cambio automático para la base decimal, si estas usando la convención numérica de C, esto es, en 0xNN, el NN será tratado como un hexadecimal y en 0NN, el NN será visto como uno octal. Vea el ejemplo:

Ejemplo

$ echo $((10)) # decimal 10 $ echo $((010)) # octal 8 $ echo $((0x10)) # hexadecimal 16 $ echo $((10+010+0x10)) # Decimal + octal + hexadecimal 64

Ah, se me olvidaba! Las expresiones aritméticas con los formatos $((...)), $[...] y con el mando let usan los mismos operadores usados en la instrucción expr, además de los operadores unários (++, --, +=, *=, ...) y condicionales que acabamos de ver.

Pruebas usando expresiones regulares

En las Conversaciones de Bar IV, lo comentamos todo sobre comandos condicionales, pero faltó uno que no existía a aquella época. En esta misma Conversación de Bar, en la sección Y toma de test llegamos a hablar de una construcción del tipo:

    [[ Expresión ]] && cmd

Donde el comando cmd será ejecutado en el caso que la expresión condicional Expressión sea verdadera. Dije que aunque Expresión podría ser definida de acuerdo con las reglas de Generación de Nombre de Archivos (File Name Generation). A partir del bash versión 3, fue incorporado a esta forma de test un operador representado por =~, cuya finalidad es hacer comparaciones con Expresiones Regulares.

Ejemplo:

$ echo $BASH_VERSION # Asumiendo que la versión de Bash es igual o superior a la 3.0.0 3.2.17(15)-release $ Cargo=Senador $ [[ $Cargo =~ ^(Governa|Sena|Verea)dora?$ ]] && echo É político É político $ Cargo=Senadora $ [[ $Cargo =~ ^(Governa|Sena|Verea)dora?$ ]] && echo É político É político $ Cargo=Diretor $ [[ $Cargo =~ ^(Governa|Sena|Verea)dora?$ ]] && echo É político $

Vamos desmenuzar la Expresión Regular ^(Governa|Sena|Verea)dora?$: esta acepta todo lo que empieza (^) por Governa, o (|) Sena, o (|) Verea, seguido de dor y seguido de una a opcional (?). El signo ($) sirve para marcar el fin de la expresión. En otras palabras esta Expresión Regular sera verdadera si recibe una de las siguientes palabras "Governador, Senador, Vereador, Governadora, Senadora e Vereadora". (En este ejemplo y su explicación hemos omitido la traducción de las constantes para hacer mas comprensible la expresión)

Coloreando la pantalla

Como usted ya había visto en la Conversación de Bar VII, el comando tput sirve para hacer casi todo lo referente la formatación de la pantalla, pero lo que no dije es que con él también se pueden usar colores frontales (de los carácteres) y de fondo. Existen también otras formas de hacer lo mismo, creo sin embargo, que la que veremos ahora, es más intuitiva (o menos “desintuitiva”). La tabla siguiente muestra los comandos para especificar los patrones de colores frontales (foreground) o de fondo (background):

Obteniendo colores con el comando tput
tput setab n Especifica n como color de fondo (background)
  Comando     Efecto  
tput setaf n Especifica n como color frontal (foreground)

Bien, ahora ya sabes como especificar la combinación de colores, pero todavía no sabes los colores. La tabla siguiente muestra los valores que la n (de la tabla anterior) debe asumir para cada color:

Valores de los colores en el comando tput
7 Gris
  Valor     Color  
0 Negro
1 Rojo
2 Verde
3 Marron
4 Azul
5 Purpura
6 Cian

En este punto ya puedes empezar a jugar con los colores.

- Pero caramba, todavía son mucho pocos!

- Y, tienes toda la razón... El problema es que todavía no te dije que si pones el terminal en modo de énfasis (tput bold), estos colores generan otros ocho. Vamos a mostar entonces la tabla definitiva de los colores:

Valores de los colores con el comando tput
7 Gris claro Blanco
  Valor     Color     Color despues de tput bold  
0 Negro Gris oscuro
1 Rojo Rojo claro
2 Verde Verde claro
3 Marron Amarillo
4 Azul Azul Brillante
5 Púrpura Rosa
6 Ciano Ciano claro

Ejemplo

Como ejemplo, veamos un script que cambiara el color de la pantalla de acuerdo a tus preferencias.

$ cat mudacor.sh #!/bin/bash tput sgr0 clear

# Cargando los 8 colores básicos para un vector Colores=(Negro Rojo Verde Marron Azul Púrpura Cian "Gris claro")

# Listando el menu de colores echo " Opc Cor = ===" # La siguiente linia significa: para i empezando desde 1; #+ cuando i es menor o igual al tamaño del vector Colores; #+ incremente el valor de i de 1 en 1 for ((i=1; i<=${#Cores[@]}; i++)) { printf "%02d %s\n" $i "${Cores[i-1]}" }

CL= until [[ $CL == 0[1-8] || $CL == [1-8] ]] do read -p " Escoje el colorr de la letra: " CL done

# Para los que tienen un bash a partir de la version 3.2 #+ el test do until de arriba podria hacerse #+ usando Expresiones Regulares. Veamos como: #+ until [[ $CL =~ 0?[1-8] ]] #+ do #+ read -p " #+ Escoje el color de la letra: " CL #+ done

CF= until [[ $CF == 0[1-8] || $CF == [1-8] ]] do read -p " Escoje el color de Fondo: " CF done

let CL-- ; let CF-- # Porque los colores varian de cero a siete tput setaf $CL tput setab $CF clear

Ganando la partida con más comodines

Estaba leyendo mis correos electrónicos cuando recibo uno de Tiago enviado a la lista de Shell Script (ya hablé de la lista y de Tiago en el Rotatório Peczenyj). Aquí va el contenido del correo electrónico:

No sé si es conocido de todos pero el shell posee, ademas del globbing normal (la expansión *, ? y [la-z] de nombres de archivos y directorios), un globbing extendido.

Creo que, en algunos casos, podria ser MUY util, eliminando uno pipe por un grep por ejemplo.

Estos son:

    ?(patron)
Debe coincidir cero o una ocurrencia de un determinado patron
    *(patron)

Debe coincidir cero o mas ocurrencias de un determinado patron
    +(patron)
Debe coincidir una o mas ocurrencias de un determinado patron
    @(patron)
Debe coincidir con exactamente una ocurrencia de un determinado patron
    !(patron)

Debe coincidir con cualquier cosa, excepto con patron

Para poder utilizár las es necesario ejecutar shopt conforme al ejemplo siguiente:

$ shopt -s extglob $ ls file filename filenamename fileutils $ ls file?(name) file filename $ ls file*(name) file filename filenamename $ ls file+(name) filename filenamename $ ls file@(name) filename $ ls file!(name) # divertido esse file filenamename fileutils $ ls file+(name|utils) filename filenamename fileutils $ ls file@(name|utils) # "lembra" um {name,utils} filename fileutils

Usando el awk para buscar por equivalencias

Ahí va una más que Tiago mandó a la lista de Shell Script de Yahoo (ya hablé de la lista y de Tiago en el Rotatório Peczenyj y en Ganando la partida con mas comodines)

Quién no pasó ya por ello: Buscar una palabra, sin embargo una letra acentuada, o no, estorbó la búsqueda?

No descubrí como hacer que el grep o el sed acepten algo semejante, pero el gawk acepta clases de equivalencia!

Mejor explicar con un ejemplo, donde voy listar el número de la línea y la ocurrencia encontrada:

$ cat dados éco eco èco êco ëco eço $ awk '/^eco/{print NR,$1}' dados 2 eco $ awk '/^e[[=c=]]o/{print NR,$1}' dados 2 eco 6 eço $ awk '/^[[=e=]]co/{print NR,$1}' dados 1 éco 2 eco 3 èco 4 êco 5 ëco

Es decir, usar [=X=] permite que la expresión encuentre la letra X estando acentuada o no (es sensible a la localización corriente!).

La sintaxis es parecida con la de las clases POSIX, cambiando los dos-puntos (:) antes y después de la clase por señales de igual (=).

Lo creí curioso y debe servir para algún caso semejante al descrito.

find – Buscando archivo por características

Si estas como el Sr. Magoo, buscando en vano un archivo, usa el mando find que sirve para buscar archivos no sólo por el nombre, sino que ademas busca por diversas características. Su sintaxis es la siguiente:

    find [camino ...] expresión [acción]

Parametros:

camino      Path del directorio a partir del cual (porque es recursivo, siempre intentará entrar en los subdirectórios "colgados" de este) irá buscando en los archivos;
expresión   Define los critérios de busqueda. Puede ser una combinación entre vários tipos de busqueda;
acción           Define que acción ejecutar con los archivos que coincidan con los critérios de busqueda definidos por expresión.

los principales critérios de busqueda definidos por expresión son:

-name Busca archivos que tengan el nombre especificado. Aquí pueden ser usados metacaracteres o carácteres comodines, sin embargo estos carácteres deberán estar entre comillas, apóstrofos o inmediatamente precedidos por una contrabarra, eso se debe a que quien tienen que expandir los comodines en el find. Si fuese el Shell que los expandiese, esto solo se haria con respecto al directorio corriente, lo que echaría por tierra la característica recursiva del find;
-user Busca archivos que tengan al usuario como propietario;
-group Busca archivos que tengan al grupo como propietario;
-type c Busca archivos que tengan el tipo c, correspondiente a la letra del tipo de archivo. Los tipos aceptados están en la siguiente tabla:
Valores de c Tipo de archivo buscado
b Archivo especial accedido por el bloque
c Archivo especial accedido por el caracter
d Directório
p Named pipe (FIFO)
f Archivo normal
l Link simbólico
s Socket
  -size ±n[unid]   Busca archivos que usan mas (+n) de n unidades unid de espacio o menos (-n) de n unidades unid de espacio.
Unidades Valor
b Bloque de 512 bytes (valor default)
c Caracteres
k Kilobytes (1024 bytes)
w Palabras (2 bytes)
-atime ±d Busca archivos a los que se accedio hace mas (+d) de d dias o menos (-d) de d dias;
-ctime ±d Busca archivos cuyo status cambio hace mas (+d) de d dias o menos (-d) de d dias;
-mtime ±d Busca archivos cuyos datos fueron modificados hace mas (+d) de d dias o menos (-d) de d dias;

Para usar mas de un critério de busqueda, haz:
     expresión1 expresión2
o
     expresión1 –a expresión2
para atender a los criterios especificados por expresión1 y expresión2;
     expresión1 –o expresión2
para atender a los criterios especificados por expresión1 o expresión2.

Las principales acciones definidas para acción son:

-print Esta opción hace que los archivos encontrados sean mostrados en la pantalla. Esta es la opción default en el Linux. En los otros sabores Unix que conozco, si no se especificase ninguna acción, ocurrirá un error;
-exec cmd {} \; Ejecuta el comando cmd. El objetivo del comando es considerado finalizado cuando se encuentra un punto-y-coma (;). La cadena {} es substituida por el nombre de cada archivo que satisface al criterio de investigación y la línea así formada es ejecutada. tal y como dijimos para la opción –name, el punto-y-coma (;) debe ser precedido por una contrabarra (\), o debe estar entre comillas o apóstrofos;
-ok cmd {} \; Lo mismo que el anterior sin embargo pregunta si se puede ejecutar la instrucción cmd sobre cada archivo que atiende al criterio de busqueda
-printf formato  Permite que se elija los campos que serán listados y formatea la salida de acuerdo con lo especificado en formato.

Ejemplos:

Para listar en pantalla (-print) todos los archivos, a partir del diretório actúal, terminados por .sh, haz:

$ find . -name \*.sh Acción no especificada –print el default ./undelete.sh ./ntod.sh estos cuatro primeros archivos fueron ./dton.sh encontrados en el directório actual. ./graph.sh ./tstsh/cotafs.sh ./tstsh/data.sh Estos cuatro fueron encontrados en el ./tstsh/velha.sh directório tstsh, bajo el directório corrente ./tstsh/charascii.sh

Necesito obtener espacio en un determinado file system con mucha urgencia, entonces voy a borrar los archivos con más de un megabyte y cuyo último acceso fue hay más de 60 días. Para eso, voy a este file system y hago:

$ find . –type f –size +1000000c –atime +60 –exec rm {} \;

Observa que en el ejemplo anterior use tres criterios de búsqueda, a saber:

-type f Todos los archivos regulares (normales)
-size +1000000c   Tamaño mayor de 1000000 de caracteres (+1000000c)
-atime +60 Último acesso hace mas de 60 (+60) dias.

Observa que entre estos tres criterios use el conector e, esto es, archivos regulares e mayores que 1MByte e sin acceso hace más de 60 días.

Para listar todos los archivos del disco terminados por .sh o .txt, haría:

$ find / -name \*.sh –o –name \*.txt –print

En este ejemplo debemos resaltar además de las contrabarras (\) antes de los asteriscos (*), el uso del –o para una u otra extensión y que el directorio inicial era el raíz (/); siendo así, que esta búsqueda se hizo en todo el disco (lo que frecuentemente es bastante lento).

Con el printf es posible formatear la salida del comando find y especificar los dados deseados. El formateo del printf es muy semejante a la del mismo comando en el lenguaje C e interpreta caracteres de formateo precedidos por un símbolo de porcentaje (%). Veamos sus efectos sobre el formateo:

Caracter Significado
%U Número del usuário (UID) propietario del archivo
%f Nombree del archivo (el path completo no aparece)
%F Indica a que tipo de file system o archivo pertence
%g Grupo al cual el archivo pertence
%G Grupo al cual el archivo pertence (GID- Numérico)
%h Path completo del archivo (todo menos el nombre)
%i Número del inode del archivo (en decimal)
%m Permisos del archivo (en octal)
%p Nombre del archivo
%s Tamaño del archivo
%u Nombre del usuário (username) propietario del archivo

También es posible formatear fechas y horas obedeciendo las tablas siguientes:

Carácter Significado
%a Fecha del último acceso
%c Fecha de creación
%t Fecha de Modificación

Los tres caracteres anteriores producen una fecha semejante a al del comando date.

Veamos un ejemplo:

$ find . -name ".b*" -printf '%t %p\n' Mon Nov 29 11:18:51 2004 ./.bash_logout Tue Nov 1 09:44:16 2005 ./.bash_profile Tue Nov 1 09:45:28 2005 ./.bashrc Fri Dec 23 20:32:31 2005 ./.bash_history

En ese ejemplo, el %p fue el responsable por poner los nombres de los archivos. En caso de ser omitido, solamente las fechas serían listadas. Observe que aunque al final se puso una /n. Sin él no habría salto de línea y la lista anterior sería un gran berenjenal.

Esas fechas también pueden ser formateadas, para eso basta pasar las letras de la tabla anterior a mayúsculas (%La, %C y %T) y usar uno de los formateadores de las dos tablas siguientes:

Tabla de formatación de tiempo
Z  Huso horário (en mi maravillosa ciudad BRST)
  Carácter     Significado  
H  Hora (00..23)
I  Hora (01..12)
k  Hora (0..23)
l  Hora (1..12)
M  Minuto (00..59)
p  AM or PM
r  Horário de 12 horas (hh:mm:ss) seguido de AM o PM
S  Segundos (00 ... 61)
T  Horário de 24-horas (hh:mm:ss)
Tabla de formatación de fechas
Y  Año con 4 dígitos
  Carácter     Significado  
a  Dia de la semana abreviado (Dom...Sab)
A  Dia de la semana completo (Domingo...Sábado)
b  Nombre del mes abreviado (Jan...Dez)
B  Dia del mes completo (Enero...Diciembre)
c  Fecha y hora completa (Fri Dec 23 15:21:41 2005)
d  Dia del mes (01...31)
D  Fecha no formateada mm/dd/aa
h  Idéntico a b
j  Dia secuencial del año (001…366)
m  Mes (01...12)
U  Semana secuencial del año. Domingo como 1º dia de la semana (00...53)
w  Dia secuencial de la semana (0..6)
W  Semana secuencial del año. Lunes como 1º dia de la semana (00...53)
x  Representación de la fecha en el formato del país (definido por $LC_ALL)
y  Año con 2 dígitos (00...99)

Para mejorar la situación, veamos unos ejemplos; sin embargo, veamos primero cuáles son los archivos del directorio corriente que empiezan por .b:

$ ls -la .b* -rw------- 1 d276707 ssup 21419 Dec 26 17:35 .bash_history -rw-r--r-- 1 d276707 ssup 24 Nov 29 2004 .bash_logout -rw-r--r-- 1 d276707 ssup 194 Nov 1 09:44 .bash_profile -rw-r--r-- 1 d276707 ssup 142 Nov 1 09:45 .bashrc

Para listar esos archivos en orden de tamaño, podemos hacer:

$ find . -name ".b*" -printf '%s\t%p\n' | sort -n 24 ./.bash_logout 142 ./.bashrc 194 ./.bash_profile 21419 ./.bash_history

En el ejemplo que acabamos de ver, el \t fue substituido por un a la salida de forma que la lista fuera más legible. Para listar los mismos archivos clasificados por fecha y hora de la última alteración:

$ find . -name ".b*" -printf '%TY-%Tm-%Td %TH:%TM:%TS %p\n' | sort 2004-11-29 11:18:51 ./.bash_logout 2005-11-01 09:44:16 ./.bash_profile 2005-11-01 09:45:28 ./.bashrc 2005-12-26 17:35:13 ./.bash_history

Y no te olvides, cualquer duda o falta de compañia para tomar una cerveza, lo único que tienes que hacer es mandarme un e-mail a julio.neves@gmail.com. Voy aprovechar tambiém para mandar mi aviso publicitario: puedes decirle a los amigos que quien quiera hacer un curso nota diez de programación en Shell que mande un e-mail para julio.neves@uniriotec.br para informarse.

Gracias y hasta la próxima!

-- HumbertoPina - 25 Jan 2007

Licencia Creative Commons - Reconocimiento y no comercial (CC) 2009 Por Visitantes del Bar de Júlio Neves.
Todo el contenido de esta página puede ser usada de acuerdo a la Creative Commons License: Atribuição-UsoNãoComercial-PermanênciaDaLicença.