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.

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.
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
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@uniriotec.br para informarse.
Gracias y hasta la próxima!
--
HumbertoPina - 25 Jan 2007