\

Aqui temos um livro livre e completo sobre Shell

Os sedentos do "saber livre" são muito benvindos.

Você está aqui: TWikiBar > TWikiBaracompanhamentos
Controles: EDITAR ANEXAR MAIS MAIS ALTERACOES IMPRIMIR - Última Atualização: [05 May 2014 - V.11]

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 todavia 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 004, 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 007, 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 y, esto es, archivos regulares y mayores que 1MByte y 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

Algunas implementaciones de Bash 4.0

Aquí en este capítulo presentaré los que es nuevo a partir de Bash 4.0. Lo que ya existia, y que fué mejorado a partir de esta versión de Bash (como las ampliaciones en los parámetros), esta siendo publicado en la sección correspondiente.

Coprocessos

A partir de la versión 4.0, Bash incorporo el comando coproc. Este nuevo intrínseco (builtin) permite dos procesos asincronicos se comuniquen y do procesos asíncronos se comuniquen y interactuen. Como cita Chet Ramey en Bash FAQ, ver. 4.01:

"Hay una nueva palabra reservada, coproc, que especifica un coprocesso: un comando asíncrono que es ejecutado con 2 pipes conectados al Shell creador. coproc puede recibir nombre. Los descriptores de archivos de entrada y salida y el PID del coproceso estan disponibles para el Shell creador en variables con nombres específicos del coproc."

George Dimitriu explica:

"El coproc es una facilidad usada en la substitución de procesos que ahora estan publicamente disponibles."

La comprobación de lo que dice Dimitriu no es complicada, quieres ver? Vea la sustitución de procesos a continuación:

$ cat <(echo xxx; sleep 3; echo yyy; sleep 3)

Vió?! El comando cat no esperó por la conclusión de los comandos entre paréntesis, pero fue ejecutado al final de cada una de ellos. Eso aconteció porque se estableció un pipe temporal/dinámico y los comandos que estaban siendo ejecutados, le mandaban sus salidas, que a su vez le mandaba para la entrada de cat.

Eso significa que los comandos de esta sustitución de procesos giraran paralelos, sincronizando solamente en las salidas de los echo con entrada de cat.

La sintaxis de un coprocesso es:

   coproc [nombre] cmd redirecionamientos

Eso creará un coprocesso llamdo nombre. Si el nombre es informado, cmd deberá ser un comando compuesto. En caso contrario (en el caso del nombre no será informado), cmd podrá ser un comando simple o compuesto.

Cuando un coproc es ejecutado, el crea un vector como el mismo nombre nombre en el Shell creador. La salida padron es unida con un pipe a un descriptor de archivo asociado a la variable ${nombre[0]} la entrada en el padron del Shell padre (recuerda que la entrada padrón de un proceso es siempre asociada por default al descriptor cero). De esta forma, la entrada del coproc es unida a la salida padrón del script, por un pipe, a un descriptor de archivos llamado ${nombre[1]}. Así, simplificando, vemos que el script mandará datos para o coproc por la variablel ${nombre[0]}, y recibirá su salida en ${nombre[1]}. Note que estos pipes serán creados antes de los direccionamientos especificados por el comando, creados antes de los redirecionamientos especificados por el comando, ya que ellos serán las entradas y salidas del coprocesso.

A partir de aquí, voy a detallar rapidamente unos estudios que hice. Esto contiene un poco de divagaciones y mucha teoría. Si tú quieres saltar para después de estos ls's, no perderás nada, pero si acompañas, puede ser bueno para la comprensión del mecanismo del coproc.

Depués de colocar un coproc activo, si él esta asociado a un descriptor de un archivo, vamos a ver lo que tiene activo en el directorio correspondiente:

$ ls -l /dev/fd lrwxrwxrwx 1 root root 13 2010-01-06 09:31 /dev/fd -> /proc/self/fd

Hummm, es un link para el /proc/self/fd... Lo que será que tiene allá?

$ ls -l /proc/self/fd total 0 lrwx------ 1 julio julio 64 2010-01-06 16:03 0 -> /dev/pts/0 lrwx------ 1 julio julio 64 2010-01-06 16:03 1 -> /dev/pts/0 lrwx------ 1 julio julio 64 2010-01-06 16:03 2 -> /dev/pts/0 lr-x------ 1 julio julio 64 2010-01-06 16:03 3 -> /proc/3127/fd

Opa, que el 0, 1 y 2 apuntaba para /dev/pts/0 yo ya sabía, pues son las entradas padrón, salida padrón y salida de errores padrón apuntando para el pseudo terminal corriente, pero que será ese maldito device 3?

Veamos:

$ ls -l /proc/$$/fd # $$ Es el PID del Shell corriente total 0 lr-x------ 1 julio julio 64 2010-01-06 09:31 0 -> /dev/pts/0 lrwx------ 1 julio julio 64 2010-01-06 09:31 1 -> /dev/pts/0 lrwx------ 1 julio julio 64 2010-01-06 09:31 2 -> /dev/pts/0 lrwx------ 1 julio julio 64 2010-01-06 16:07 255 -> /dev/pts/0 l-wx------ 1 julio julio 64 2010-01-06 16:07 54 -> pipe:[168521] l-wx------ 1 julio julio 64 2010-01-06 16:07 56 -> pipe:[124461] l-wx------ 1 julio julio 64 2010-01-06 16:07 58 -> pipe:[122927] lr-x------ 1 julio julio 64 2010-01-06 16:07 59 -> pipe:[168520] l-wx------ 1 julio julio 64 2010-01-06 16:07 60 -> pipe:[121302] lr-x------ 1 julio julio 64 2010-01-06 16:07 61 -> pipe:[124460] lr-x------ 1 julio julio 64 2010-01-06 16:07 62 -> pipe:[122926] lr-x------ 1 julio julio 64 2010-01-06 16:07 63 -> pipe:[121301]

Opa, ahí están los links aputando para los pipes. Esa cantidad de archivos de pipe que fué listado, debe ser porque estaba testeando exaustivamente esa nueva facilidad del Bash.

Para terminar esta teoria aburrida, falta decir que el PID del Shell generado para interpretar o coproc puede ser obtenido en la variable $nombre_PID es el comando wait puede ser usado para esperar por el fin del coprocesso. El código de retorno del coprocesso ($?) es el mismo de cmd.

Ejemplo:

Vamos comenzar con los más simples: un ejemplo sin nombre y directo en el prompt:

$ coproc while read Entra # coproc activo > do > echo -=-=- $Entra -=-=- > done [2] 3030 $ echo Hola >&${COPROC[1]} # Manda Hola para el pipe de salida $ read -u ${COPROC[0]} Sale # Lee del pipe de entrada $ echo $Sale -=-=- Hola -=-=- $ kill $COPROC_PID # Eso no puede ser olvidado...

Como puedes ver, el vector COPROC, está asociado a dos pipes; el ${COPROC[1]} que contiene la dirección del pipe de salida, es por eso la salida del echo está redireccionada para él y ${COPROC[0]} que contiene la dirección del pipe de entrada, y por eso usamos la opción -u del read que lee datos a partir de un descriptor de archivo definido, al revés de la entrada padrón.

Como él coprocesso utilizaba la sintaxis sin nombre, el padrón del nombre del vector es COPROC.

Solo una más "pequeña teoria" aburrida:

$ echo ${COPROC[@]} # Lista todos los elementos del vector 59 54

Como vistes ${COPROC[0]} estaba usando el pipe apuntado por /proc/$$/fd/59 y ${COPROC[1]} usaba /proc/$$/fd/54. Ahora paramos la teoría de verdad! Vamos a usar nombre en este mismo ejemplo, para ver que poco cambia

$ coproc test { > while read Entra > do > echo -=-=- $Entra -=-=- > done > } [6] 3192 $ echo Hola >&${test[1]} $ read -u ${test[0]} Salida $ echo $Salida -=-=- Hola -=-=- $ kill $test_PID

En éste momento, es bueno mostra una cosa interesante: Cuales son los procesos en ejecución?

$ ps # Solamente en el Bash en ejecución PID TTY TIME CMD 1900 pts/0 00:00:01 bash 2882 pts/0 00:00:00 ps

Vamos ejecutar 2 coprocessos simultáneos:

$ coproc nombre1 { # Coprocesso nombre1 > while read x > do > echo $x > done; } [1] 2883 $ coproc nombre2 { # Coprocesso nombre2 > while read y > do > echo $y > done; } bash: aviso: execute_coproc: coproc [2883:nombre1] still exists [2] 2884

Jiiii! Me parece que hubo un problema! Pero será que si hubo un problema? Observe que además del PID 2883 del nombre1, el tambien me devolvió el PID 2884, que debe ser del nombre2. Vamos a ver lo que está pasando:

$ ps PID TTY TIME CMD 1900 pts/0 00:00:01 bash Ese yá existia 2883 pts/0 00:00:00 bash Ese está ejecutando nombre1 2884 pts/0 00:00:00 bash Ese está ejecutando nombre2 2885 pts/0 00:00:00 ps

Parece que fué solo un aviso, pues los dos PIDs informados cuando iniciamos los dos coprocessos, estan activos. Entonces vamos a testear esos 2 tipos:

$ echo xxxxxxxxx >&${nombre1[1]} # Mandando cadena para nombre1 $ echo yyyyyyyyy >&${nombre2[1]} # Mandando cadena para nombre2 $ read -u ${nombre1[0]} Recibe $ echo $Recibe xxxxxxxxx $ read -u ${nombre2[0]} Recibe $ echo $Recibe yyyyyyyyy $ kill $nombre1_PID $ kill $nombre2_PID

Vectores asociativos

A partir de Bash 4.0, pasó a existir el vector asociativo. Llamado vector asociativo, aquellos cuyos indices son alfabéticos. Las reglas que valen para los vectores enteros, valen también para los asociativos, por eso antes de valorar estos últimos, es obligatorio declararlos.

Ejemplo:

$ declare -A Animales # Obligatorio para el vector asociativo $ Animales[caballo]=doméstico $ Animales[zebra]=salvaje $ Animales[gato]=doméstico % $ Animales[tigre]=salvaje

Pinguim com placa de atenção (em espanhol) Es imposible generar todos los elementos de una sola vez, como en los vectores enteros. Si fuera así, la sintaxis no funciona:
Animales=([caballo]=doméstico [zebra]=salvaje  [gato]=doméstico [tigre]=salvaje)

$ echo ${Animales[@]} doméstico salvaje doméstico salvaje $ echo ${!Animales[@]} gato zebra caballo tigre

Observe que los valores no están ordenados, quedan almazenados en el orden que son creados, a diferencia de los vectores enteros que quedan ordenados numéricamente.

Supongo que ese vector tuviese centenas de elementos, para listar separadamente los domésticos de los salvajes, podriamos hacer un script asi:

$ cat animal.sh #!/bin/bash # Separa animales salvajes de los domésticos declare -A Animales Animales[caballo]=doméstico # Creando vector para el test Animales[zebra]=salvaje # Creando vector para el test Animales[gato]=doméstico # Creando vector para el test Animales[tigre]=salvaje # Creando vector para el test Animales[oso pardo]=salvaje # Creando vector para el test for Animal in "${!Animales[@]}" # Recorriendo vector por el indice do if [[ "${Animales[$Animal]}" == salvaje ]] then Sel=("${Sel[@]}" "$Animal") # Generando vector p/ salvajes else Dom=("${Dom[@]}" "$Animal") # Generando vector p/ domésticos fi done # Operador condicional, usado para descubrir cual #+ El vector tiene mas elementos. Vea detalles en la sección #+ El interpretador aritmético del Shell Mayor=$[${#Dom[@]}>${#Sel[@]}?${#Dom[@]}:${#Sel[@]}] clear tput bold; printf "%-15s%-15s\n" Domésticos Salvajes; tput sgr0 for ((i=0; i<$Mayor; i++)) { tput cup $[1+i] 0; echo ${Dom[i]} tput cup $[1+i] 14; echo ${Sel[i]} }

Me gustaría llamar tú atención para un detalle: en este script me referí a un elemento de vector asociativo empleando ${Animales[$Animal]} me referí a un elemento de un vector usando ${Sel[i]}. O sea, cuando usamos una variable como indice de un vector entero, no es necesario agregarle un ($), mientras que en el vector asociativo, el símbolo de ($) es obligatorio.

Leyendo un archivo para un vector

Todavia hablando de Bash 4.0, es que él surge con otra novedad: el comando intrínseco (builtin) mapfile, cuya finalidad es mover un archivo de texto entero dentro de un vector, sim loop o substitución de comando.

    - EPA! Eso debe ser muy rápido!

    - Y es. Haga el test y compruebe!

Ejemplo:

$ cat frutas abacate manzana frutilla pera tangerina uva $ mapfile vet < frutas # Mandando frutas para el vector vet $ echo ${vet[@]} # Listando todos los elementos de vet abacate manzana frutilla pera tangerina uva

Obtendriamos un resultado idéntico si hicieramos:

$ vet=($(cat frutas))

Por eso seria mas lento porque la substitución del comando es ejecutada en un subshell Una forma de hacer esto que ahora me viene a la cabeza es leer el archivo con la opción -a del comando read. Vamos a ver como seria el comportamiento de esto:

$ read -a vet < frutas $ echo ${vet[@]} abacate

Como dió para percibir, fué leido solamente el primero registro de las frutas.

Caja baja para alta y viceversa

Usando ampliaciones de parámetro

  • ${parâmetro^}
  • ${parâmetro,}
Essas expansões de parâmetros foram introduzidas a partir do Bash 4.0 e modificam a caixa das letras do texto que está sendo expandido. Quando usamos circunflexo (^), a expansão é feita para maiúsculas e quando usamos vírgula (,), a expansão é feita para minúsculas.

Exemplo:

$ Nome="botelho" $ echo ${Nome^} Botelho $ echo ${Nome^^} BOTELHO $ Nome="botelho carvalho" $ echo ${Nome^} Botelho carvalho # É pena que não fique Botelho Carvalho...

Um fragmento de script que pode facilitar a sua vida:

read -p "Deseja continuar (s/n)? "
[[ ${REPLY^} == N ]] && exit

Esta forma evita testarmos se a resposta dada foi um N (maiúsculo) ou um n (minúsculo).

Usando declaratives

Podemos hacer algo parecido, pero de otra manera. Ahora, para que tengamos solamente mayúsculas en una variable tambien podemos declararlas usando la opción -u ( de uppercase = mayúscula). Vea:

$ declare -u Maiusc

Una vez declaradas, vea este ejemplo

$ read -p "Tecle algo: " Maiusc # La variable Maiusc recibirá lo que sea tecleado Teclee algo: convierte a mayúsculas $ echo $Maiusc CONVIERTE A MAYÚSCULAS

Lo contrario de esto seria si usaramos la opción -l (de lowercase = minúscula). Vea:

$ declare -l Minusc $ read -p "Tecle algo: " Minusc # La variable Minusc recibirá lo que sea tecleado Teclee algo: CONVIERTE PARA MINÚSCULAS $ echo $Minusc convierte para minúsculas

Todo lo que fue dicho sobre estas conversiones, solo es válido despues que declare sea hecho. Observe:

$ var="xxxx" # Voy a atribuir antes de declarar $ declare -u var $ echo $var xxxx # Nada cambio, continua en minúscula $ var="xxxx" # Despues de la declaración $ echo $var XXXX # Ahora funcionó...

Nuevas implementaciones en el comando case

Bash 4.0 introdujo 2 nuevas facilidades en el comando case. A partir de esta versión, existen 2 terminadores mas de bloques además de ;;, que son: ;;& - Cuando un bloque de comandos fuera cerrado con este argumento, el programa no saldra del case, pero testeará los proximos padrones; ;& - Este caso, el próximo bloque será ejecutado, sin ni siquiera chequear su padrón.

Ejemplo:

Vea este fragmente de código adaptado de http://tldp.org/LDP/abs/html/bashver4.html:

case "$1" in
    [[:print:]] )  echo $1 es un caracter imprimible;;&
    # O terminador ;;& testeará el próximo padrón padrão
    [[:alnum:]] )  echo $1 es un caracter alfa/numérico;;&
    [[:alpha:]] )  echo $1 es un caracter alfabético   ;;&
    [[:lower:]] )  echo $1 es una letra minúscula    ;;&
    [[:digit:]] )  echo $1 es un caracter numérico  ;&
    # O terminador ;& ejecutará el próximo bloque...
    %%%@@@@@    )  echo "************************"  ;;
#   ^^^^^^^^  ... igual con un padrón loco.
esac

La ejecución de este código pasando 3 como parámetro, resultaria:

3 es un caracter imprimible 
3 es un caracter  alfa/numérico 
3 es un caracter numérico
********************************

Pasando m:

m  es un caracter imprimible  
m es un caracter  alfa/numérico 
m es un caracter alfabético
m es una letra minúscula

Pasando / :

/ es un caracter imprimible

Expansión de llaves

Bash 4.0 incorporó dos nuevas formas de hacer la expansión de llaves:

  • Secuencia numérica con relleno de ceros a la izquierda;
  • Posibilidad de usar un incremento (pasar) en una secuencia numérica.

Ejemplos:

$ echo {0010..0019} # Rellenando con 2 ceros a la izquierda 0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 $ echo {05..15} # Rellenando con ceros a la izquierda 05 06 07 08 09 10 11 12 13 14 15 $ echo {-5..19..3} # Incrementando de 3 en 3 (Passo 3) -5 -2 1 4 7 10 13 16 19 $ echo {000..100..10} # Rellenando con ceros y paso 10 000 010 020 030 040 050 060 070 080 090 100

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@uniriotec.br para informarse.

Gracias y hasta la próxima!

-- RandolphChaves - 03 Feb 2012


Licença Creative Commons - Atribuição e Não Comercial (CC) 2017 Pelos Frequentadores do Bar do Júlio Neves.
Todo o conteúdo desta página pode ser utilizado segundo os termos da Creative Commons License: Atribuição-UsoNãoComercial-PermanênciaDaLicença.