En GNU/Linux tenemos dos comandos que suelen ir de la mano que permiten extraer los primeros y últimos elementos de un listado.
Su uso más habitual es extraer las primeras líneas de un fichero con head o las últimas con tail.
Veamos un ejemplo sencillo sobre un fichero de texto con "Un soneto me manda hacer Violante" de Lope de Vega:
head violante.txt
Un soneto me manda hacer Violante
que en mi vida me he visto en tanto aprieto;
catorce versos dicen que es soneto;
burla burlando van los tres delante.
Yo pensé que no hallara consonante,
y estoy a la mitad de otro cuarteto;
mas si me veo en el primer terceto,
no hay cosa en los cuartetos que me espante.
tail violante.txt
mas si me veo en el primer terceto,
no hay cosa en los cuartetos que me espante.
Por el primer terceto voy entrando,
y parece que entré con pie derecho,
pues fin con este verso le voy dando.
Ya estoy en el segundo, y aun sospecho
que voy los trece versos acabando;
contad si son catorce, y está hecho.
Por defecto y sin pasarle ningún argumento más que el fichero a listar, tanto tail como head muestran diez líneas. Head las diez primeras líneas, tail las diez últimas líneas.
Pero podemos cambiar el número de líneas a mostrar con -n $numerodelineas
Veamos el primer cuarteto:
head -4 violante.txt
Un soneto me manda hacer Violante
que en mi vida me he visto en tanto aprieto;
catorce versos dicen que es soneto;
burla burlando van los tres delante.
Y el último terceto:
tail -n 3 violante.txt
Ya estoy en el segundo, y aun sospecho
que voy los trece versos acabando;
contad si son catorce, y está hecho.
Otra posibilidad que le podemos decir a tail es que muestre desde una determinada línea hasta el final.
tail -n+13 violante.txt
pues fin con este verso le voy dando.
Ya estoy en el segundo, y aun sospecho
que voy los trece versos acabando;
contad si son catorce, y está hecho.
Y si en lugar de mostrar las primeras y las últimas líneas lo que queremos es mostrar líneas centrales, lo que vamos a indicarles es que trabajen juntos: head, muestra las X primeras líneas y se las das a tail, para que seleccione las Y últimas líneas que les pases. Algo así:
head -n 13 violante.txt | tail -n 8
Yo pensé que no hallara consonante,
y estoy a la mitad de otro cuarteto;
mas si me veo en el primer terceto,
no hay cosa en los cuartetos que me espante.
Por el primer terceto voy entrando,
y parece que entré con pie derecho,
pues fin con este verso le voy dando.
Ambos comandos tienen una opción, -c que le indican que, en lugar de trabajar con líneas trabajen con caracteres:
head -c 30 violante.txt
Un soneto me manda hacer Viola
tail -c 30 violante.txt
i son catorce, y está hecho.
Para hacer que tail y head muestren el nombre del fichero, se lo indicaremos con -v:
tail -n+13 -v violante.txt
==> violante.txt <==
pues fin con este verso le voy dando.
Ya estoy en el segundo, y aun sospecho
que voy los trece versos acabando;
contad si son catorce, y está hecho.
Sin embargo, si lo que queremos es que no muestre el nombre del fichero, lo indicaremos con -q:
tail -n+13 -q violante.txt
pues fin con este verso le voy dando.
Ya estoy en el segundo, y aun sospecho
que voy los trece versos acabando;
contad si son catorce, y está hecho.
Esto es básicamente lo que ha estado haciendo hasta ahora, pero ¿qué pasaría si queremos mostrar extractos de varios ficheros?
Le podemos indicar a head o a tail que muestren extractos de multiples ficheros, y aquí si que viene bien que indique el nombre del fichero. Quizá no en este ejemplo, ya que el nombre del fichero es la primera línea sin espacios, pero en otras ocasiones, sí.
head -v lope/*
==> lope/albaniayaceaquifabiosuspira.txt <==
Albania yace aquí, Fabio suspira,
matóla un parto sin sazón, dejando
la envidia alegre, y al Amor llorando;
pues ya cualquiera fuerza le retira.
El Tajo crece por mostrar su ira
y corre, de la Muerte murmurando;
párase el sol, el túmulo mirando,
temiendo en sí, lo que en Albania mira.
==> lope/amormilanoshaquemehasjurado.txt <==
Amor, mil años ha que me has jurado
pagarme aquella deuda en plazos breves;
mira que nunca pagas lo que debes,
que esto sólo no tienes de hombre honrado.
Muchas veces, Amor, me has engañado
con firmas falsas y esperanzas leves;
a estelionatos con mi fe te atreves,
jurando darme lo que tienes dado.
==> lope/laclaraluzenlasestrellaspuesta.txt <==
La clara luz en las estrellas puesta
del fogoso León por alta parte
bañaba el sol, cuando Acidalia y Marte
en Chipre estaban una ardiente siesta.
La diosa por hacerle gusto y fiesta
la túnica y el velo deja aparte,
sus armas toma y de la selva parte,
del yelmo y plumas y el arnés compuesta.
==> lope/violante.txt <==
Un soneto me manda hacer Violante
que en mi vida me he visto en tanto aprieto;
catorce versos dicen que es soneto;
burla burlando van los tres delante.
Yo pensé que no hallara consonante,
y estoy a la mitad de otro cuarteto;
mas si me veo en el primer terceto,
no hay cosa en los cuartetos que me espante.
==> lope/yanoquieromasbienquesoloamaros.txt <==
Ya no quiero más bien que sólo amaros
ni más vida, Lucinda, que ofreceros
la que me dais, cuando merezco veros,
ni ver más luz que vuestros ojos claros.
Para vivir me basta desearos,
para ser venturoso conoceros,
para admirar el mundo engrandeceros
y para ser Eróstrato abrasaros.
O le podemos decir que no muestre el nombre del fichero ni que separe las líneas de los distintos ficheros con -q. Veamos el último terceto de cada uno de estos sonetos sin nombre:tail -q -n 3 lope/*
Venganza fue para que ejemplo quede
que quien fue basilisco en dar veneno,
muriese como víbora en el parto.
Mas, ¿cómo pagarás, Amor, si has hecho
pleito de acreedores por mil años
y, en buscando tu hacienda, estás desnudo?
Venus le respondió: «Cuando te atrevas
verás cuanto mejor te vence armada
la que desnuda te venció primero».
Ya estoy en el segundo, y aun sospecho
que voy los trece versos acabando;
contad si son catorce, y está hecho.
Que entre tales riquezas y tesoros
mis lágrimas, mis versos, mis suspiros
de olvido y tiempo vivirán seguros.
Un poco lioso todo, pero para eso hemos elegido un autor barroco ;)
Estos dos comandos nos permiten saber, por ejemplo, cuál es el primer y último fichero de un directorio:
ls lope/ | head -n 1
albaniayaceaquifabiosuspira.txt
ls lope/ | tail -n 1
yanoquieromasbienquesoloamaros.txt
Esto viene muy bien cuando numeramos los ficheros y queremos saber cuál es el último.
Supongamos que tenemos un directorio que se llame "fotogramas" con un número de fotogramas que desconocemos y queremos saber cuál es el último. Le podemos indicar en un script que lo extraiga y lo guarde en una variable:
ultimofotograma=$(ls $directoriofotogramas | tail -n 1)
Dentro de un bucle for, podemos ir cambiando el valor de head manteniendo tail constante a -n 1 y lo que harán será ir extrayendo uno a uno los elementos que le indiquemos.
Veamos un ejemplo:
#!/bin/bashcolores=""for i in $(seq 1 5)do color=$(convert -list color | grep LightBlue | cut -d " " -f 1 | head -n $i | tail -n 1) colores=$colores"xc:"$color" "doneconvert -size 500x100 $colores -append -motion-blur 0x100+90 celeste.png
Con este script generamos una imagen como esta:

Porque head y tail no sólo sirven para trabajar con ficheros de texto. También ayudan a hacer degradados ;)
En GNU/Linux tenemos dos comandos que suelen ir de la mano que permiten extraer los primeros y últimos elementos de un listado.
Su uso más habitual es extraer las primeras líneas de un fichero con head o las últimas con tail.
Veamos un ejemplo sencillo sobre un fichero de texto con "Un soneto me manda hacer Violante" de Lope de Vega:
head violante.txt
Un soneto me manda hacer Violante
que en mi vida me he visto en tanto aprieto;
catorce versos dicen que es soneto;
burla burlando van los tres delante.
Yo pensé que no hallara consonante,
y estoy a la mitad de otro cuarteto;
mas si me veo en el primer terceto,
no hay cosa en los cuartetos que me espante.
tail violante.txt
mas si me veo en el primer terceto,
no hay cosa en los cuartetos que me espante.
Por el primer terceto voy entrando,
y parece que entré con pie derecho,
pues fin con este verso le voy dando.
Ya estoy en el segundo, y aun sospecho
que voy los trece versos acabando;
contad si son catorce, y está hecho.
Por defecto y sin pasarle ningún argumento más que el fichero a listar, tanto tail como head muestran diez líneas. Head las diez primeras líneas, tail las diez últimas líneas.
Pero podemos cambiar el número de líneas a mostrar con -n $numerodelineas
Veamos el primer cuarteto:
head -4 violante.txt
Un soneto me manda hacer Violante
que en mi vida me he visto en tanto aprieto;
catorce versos dicen que es soneto;
burla burlando van los tres delante.
Y el último terceto:
tail -n 3 violante.txt
Ya estoy en el segundo, y aun sospecho
que voy los trece versos acabando;
contad si son catorce, y está hecho.
Otra posibilidad que le podemos decir a tail es que muestre desde una determinada línea hasta el final.
tail -n+13 violante.txt
pues fin con este verso le voy dando.
Ya estoy en el segundo, y aun sospecho
que voy los trece versos acabando;
contad si son catorce, y está hecho.
Y si en lugar de mostrar las primeras y las últimas líneas lo que queremos es mostrar líneas centrales, lo que vamos a indicarles es que trabajen juntos: head, muestra las X primeras líneas y se las das a tail, para que seleccione las Y últimas líneas que les pases. Algo así:
head -n 13 violante.txt | tail -n 8
Yo pensé que no hallara consonante,
y estoy a la mitad de otro cuarteto;
mas si me veo en el primer terceto,
no hay cosa en los cuartetos que me espante.
Por el primer terceto voy entrando,
y parece que entré con pie derecho,
pues fin con este verso le voy dando.
Ambos comandos tienen una opción, -c que le indican que, en lugar de trabajar con líneas trabajen con caracteres:
head -c 30 violante.txt
Un soneto me manda hacer Viola
tail -c 30 violante.txt
i son catorce, y está hecho.
Para hacer que tail y head muestren el nombre del fichero, se lo indicaremos con -v:
tail -n+13 -v violante.txt
==> violante.txt <==
pues fin con este verso le voy dando.
Ya estoy en el segundo, y aun sospecho
que voy los trece versos acabando;
contad si son catorce, y está hecho.
Sin embargo, si lo que queremos es que no muestre el nombre del fichero, lo indicaremos con -q:
tail -n+13 -q violante.txt
pues fin con este verso le voy dando.
Ya estoy en el segundo, y aun sospecho
que voy los trece versos acabando;
contad si son catorce, y está hecho.
Esto es básicamente lo que ha estado haciendo hasta ahora, pero ¿qué pasaría si queremos mostrar extractos de varios ficheros?
Le podemos indicar a head o a tail que muestren extractos de multiples ficheros, y aquí si que viene bien que indique el nombre del fichero. Quizá no en este ejemplo, ya que el nombre del fichero es la primera línea sin espacios, pero en otras ocasiones, sí.
head -v lope/*
==> lope/albaniayaceaquifabiosuspira.txt <==
Albania yace aquí, Fabio suspira,
matóla un parto sin sazón, dejando
la envidia alegre, y al Amor llorando;
pues ya cualquiera fuerza le retira.
El Tajo crece por mostrar su ira
y corre, de la Muerte murmurando;
párase el sol, el túmulo mirando,
temiendo en sí, lo que en Albania mira.
==> lope/amormilanoshaquemehasjurado.txt <==
Amor, mil años ha que me has jurado
pagarme aquella deuda en plazos breves;
mira que nunca pagas lo que debes,
que esto sólo no tienes de hombre honrado.
Muchas veces, Amor, me has engañado
con firmas falsas y esperanzas leves;
a estelionatos con mi fe te atreves,
jurando darme lo que tienes dado.
==> lope/laclaraluzenlasestrellaspuesta.txt <==
La clara luz en las estrellas puesta
del fogoso León por alta parte
bañaba el sol, cuando Acidalia y Marte
en Chipre estaban una ardiente siesta.
La diosa por hacerle gusto y fiesta
la túnica y el velo deja aparte,
sus armas toma y de la selva parte,
del yelmo y plumas y el arnés compuesta.
==> lope/violante.txt <==
Un soneto me manda hacer Violante
que en mi vida me he visto en tanto aprieto;
catorce versos dicen que es soneto;
burla burlando van los tres delante.
Yo pensé que no hallara consonante,
y estoy a la mitad de otro cuarteto;
mas si me veo en el primer terceto,
no hay cosa en los cuartetos que me espante.
==> lope/yanoquieromasbienquesoloamaros.txt <==
Ya no quiero más bien que sólo amaros
ni más vida, Lucinda, que ofreceros
la que me dais, cuando merezco veros,
ni ver más luz que vuestros ojos claros.
Para vivir me basta desearos,
para ser venturoso conoceros,
para admirar el mundo engrandeceros
y para ser Eróstrato abrasaros.
O le podemos decir que no muestre el nombre del fichero ni que separe las líneas de los distintos ficheros con -q. Veamos el último terceto de cada uno de estos sonetos sin nombre:tail -q -n 3 lope/*
Venganza fue para que ejemplo quede
que quien fue basilisco en dar veneno,
muriese como víbora en el parto.
Mas, ¿cómo pagarás, Amor, si has hecho
pleito de acreedores por mil años
y, en buscando tu hacienda, estás desnudo?
Venus le respondió: «Cuando te atrevas
verás cuanto mejor te vence armada
la que desnuda te venció primero».
Ya estoy en el segundo, y aun sospecho
que voy los trece versos acabando;
contad si son catorce, y está hecho.
Que entre tales riquezas y tesoros
mis lágrimas, mis versos, mis suspiros
de olvido y tiempo vivirán seguros.
Un poco lioso todo, pero para eso hemos elegido un autor barroco ;)
Estos dos comandos nos permiten saber, por ejemplo, cuál es el primer y último fichero de un directorio:
ls lope/ | head -n 1
albaniayaceaquifabiosuspira.txt
ls lope/ | tail -n 1
yanoquieromasbienquesoloamaros.txt
Esto viene muy bien cuando numeramos los ficheros y queremos saber cuál es el último.
Supongamos que tenemos un directorio que se llame "fotogramas" con un número de fotogramas que desconocemos y queremos saber cuál es el último. Le podemos indicar en un script que lo extraiga y lo guarde en una variable:
ultimofotograma=$(ls $directoriofotogramas | tail -n 1)
Dentro de un bucle for, podemos ir cambiando el valor de head manteniendo tail constante a -n 1 y lo que harán será ir extrayendo uno a uno los elementos que le indiquemos.
Veamos un ejemplo:
#!/bin/bashcolores=""for i in $(seq 1 5)do color=$(convert -list color | grep LightBlue | cut -d " " -f 1 | head -n $i | tail -n 1) colores=$colores"xc:"$color" "doneconvert -size 500x100 $colores -append -motion-blur 0x100+90 celeste.png
Con este script generamos una imagen como esta:

Porque head y tail no sólo sirven para trabajar con ficheros de texto. También ayudan a hacer degradados ;)
Ya hemos visto que operar con decimales en Bash no es imposible, aunque es farragoso. Ahora veremos que pasa lo mismo para comparar decimales. Pero lo explico y dejo una función para que quien tenga que comparar, la copie en su script.
Primero comprobaremos si le hemos pasado bien los parámetros $1 y $2 a la función. Comprobamos si existen y tienen contenido. Si esto ocurre, comprobaremos que realmente son números que opcionalmente tienen un punto.
En caso de que sólo hubiera una variable numérica, se devuelve esa, ya que esa será la mayor.
Si las variables no fueran numéricas, se devuelve -1 de error.
Si ambas variables son nuḿéricas, se dividen en la parte entera y la parte decimal. Es decir, se extrae la subcadena desde el inicio hasta el punto, que es la parte entera, y la subcadena desde el punto hasta el fina, que es la parte decimal.
Comparamos las partes enteras. Si las partes enteras son distintas, el número mayor es el que tenga la parte entera mayor.
En caso de que las partes enteras sean iguales, seguimos haciendo comprobaciones:
Como recortamos subcadenas, igualamos su longitud con ceros al final de la subcadena más corta. Para que 0.02 no de mayor que 0.1.
Una vez que sabemos que la función ha recibido dos variables, que estas son numéricas con o sin punto, que hayamos separado la parte entera y la decimal, que las partes enteras son iguales y que las partes decimales tienen los mismos caracteres, comparamos los decimales.
Y devolvemos el parámetro cuya parte decimal sea mayor.
En caso de que la parte entera sea igual y la parte decimal sea igual tamibén, es que los dos números son iguales. Así que nos da igual cuál devolver. Devolveremos $1.
Y aquí el script con comentarios porque la función es un poco liosa:
<
[code]#!/bin/bash
function cualesmayor()
{
# Función que recibe dos parámetros con números decimales o no
# y devuelve el mayor de los dos
# Comprobamos que recibimos los dos parámetros,
# si recibimos solo uno, devolvemos ese
if [ "$1" ] && [ "$2" ]
then
# Existen ambas variables, por lo que podemos compararlas
# Comprobamos si ambos parámetros están formados únicamente
# por números y un único .
caracteresvalidos='^[0-9]+([.][0-9]+)?$'
if [[ $1 =~ $caracteresvalidos ]] && [[ $2 =~ $caracteresvalidos ]]
then
# Ambos son numéricos. Por lo que separaremos
# en la parte entera y la parte decimal
# Dónde está el punto. Si no hubiera punto, la posición es 0
let posicionpunto1=$(expr index $1 ".")
let posicionpunto2=$(expr index $2 ".")
# Extraemos las partes enteras.
if [ $posicionpunto1 -lt 0 ]
then
let parteentera1=$1
else
let numerodigitosparteentera1=$posicionpunto1-1
let parteentera1=${1:0:$numerodigitosparteentera1}
partedecimal1=${1:$posicionpunto1}
let numerodigitosparteentera2=$posicionpunto2-1
let parteentera2=${2:0:$numerodigitosparteentera2}
partedecimal2=${2:$posicionpunto2}
# Comprobamos las partes enteras
if [ $parteentera1 -gt $parteentera2 ]
then
echo $1
elif [ $parteentera1 -lt $parteentera2 ]
then
echo $2
else
# Ambas partes enteras son iguales
# Comprobamos las partes decimales
# Primero igualamos con ceros al final
# la longitud de las cadenas
tamdecimales1=${#partedecimal1}
tamdecimales2=${#partedecimal2}
if [ $tamdecimales1 -gt $tamdecimales2 ]
then
let diferencia=$tamdecimales1-$tamdecimales2
for i in $(seq 1 $diferencia)
do
partedecimal2=$partedecimal2"0"
done
elif [ $tamdecimales1 -lt $tamdecimales2 ]
then
let diferencia=$tamdecimales2-$tamdecimales1
for i in $(seq 1 $diferencia)
do
partedecimal1=$partedecimal1"0"
done
fi
if [ $partedecimal1 -gt $partedecimal2 ]
then
echo $1
elif [ $partedecimal1 -lt $partedecimal2 ]
then
echo $partedecimal2
else
# Ambas partes enteras son iguales
# Ambas partes decimales son iguales
# Ambos números son iguales
# Devolvemos el primero
echo $1
fi
fi
fi
else
echo -1
fi
else
if [ "$1" ] && [ ! "$2" ]
then
# $1 existe y no está vacío y $2 no existe o está vacío.
# Lo contrario nunca ocurrirá, ya que sin $1 no puede haber $2
echo $1
else
# Si llegamos aquí es porque no existe $1
echo -1
fi
fi
}
let operador1=3
let operador2=2
let operador3=5
let operador4=1
division1=$(echo "scale=4; $operador1/$operador2" | bc)
division2=$(echo "scale=4; $operador3/$operador4" | bc)
mayor=$(cualesmayor $division1 $division2)
echo "El primer resultado es "$division1" y el segundo resultado, "$division2". El mayor es "$mayor[code]
Para saber si una variable existe y está inicializada con un valor distinto a vacío, es decir, que no se ha inicializado con:
variable=""
Lo podemos comprobar con [ "$variable" ]
Veamos un ejemplo y vamos a ir evolucionándolo para ver sus posibilidades:
#!/bin/bashif [ "$variable" ]then echo "La variable existe y no está vacía"else echo "La variable no existe o está vacía"fi
Devuelve:
La variable no existe o está vacía
Ahora vamos a probar con la variable inicializada vacía:
#!/bin/bash
variable=""if [ "$variable" ]then echo "La variable existe y no está vacía"else echo "La variable no existe o está vacía"fi
Y nos vuelve a dar:
La variable no existe o está vacía
Así que vamos a dar un valor. En un alarde de imaginación, a la variable variable le voy a dar un valor de valor:
#!/bin/bash
variable="valor"if [ "$variable" ]then echo "La variable existe y no está vacía"else echo "La variable no existe o está vacía"fi
Y al ejecutarlo, devuelve:
La variable existe y no está vacía
Para comprobar que no está vacía, lo que podemos hacer es comprobar si existe. Si existe y no contiene nada es que su valor es "". Esto lo haremos con [ -n "${variable-unset}" ], que dará verdadero si no existe:
#!/bin/bash
variable="valor"if [ "$variable" ]then echo "La variable existe y no está vacía"else if [ -n "${variable-unset}" ] then echo "La variable no existe" else echo "La variable existe pero está vacía" fifi
Que nos devuelve:
La variable existe y no está vacía
Y ahora probaremos si la cadena está vacía:
#!/bin/bash
variable=""if [ "$variable" ]then echo "La variable existe y no está vacía"else if [ -n "${variable-unset}" ] then echo "La variable no existe" else echo "La variable existe pero está vacía" fifi
Que devuelve:
La variable existe pero está vacía
Y sin variable (la comento):
#!/bin/bash
# variable=""if [ "$variable" ]then echo "La variable existe y no está vacía"else if [ -n "${variable-unset}" ] then echo "La variable no existe" else echo "La variable existe pero está vacía" fifi
Que devuelve:
La variable no existe
Para hacer operaciones sencillas en Bash podemos usar let, pero únicamente operaciones muy sencillas. Veamos un ejemplo:
#!/bin/bashlet operador1=3let operador2=2let suma=$operador1+$operador2let resta=$operador1-$operador2let multiplicacion=$operador1*$operador2let division=$operador1/$operador2let modulo=$operador1%$operador2echo "La suma de "$operador1" + "$operador2" es "$sumaecho "La diferencia de "$operador1" + "$operador2" es "$restaecho "El producto de "$operador1" x "$operador2" es "$multiplicacionecho "El cociente de "$operador1" / "$operador2" es "$divisionecho "El resto de "$operador1" / "$operador2" es "$modulo
Al ejecutarlo nos da este resultado:
La suma de 3 + 2 es 5
La diferencia de 3 + 2 es 1
El producto de 3 + 2 es 6
El cociente de 3 / 2 es 1
El resto de 3 / 2 es 1
Ufffff... ¡Pi es 3 exactamente!
No, no, que no te de un infarto, que pi no es 3. Y en Bash podemos calcularlo sin estos sobresaltos. Aunque no lo haremos con let, sino con bc.
De hecho, podemos indicar cuántos decimales queremos a través de scale. Veamos una aproximación a pi:
echo "scale=2; 355/113" | bc
3.14
Y con cuatro decimales:
echo "scale=4; 355/113" | bc
3.1415
Y con seis decimales:
echo "scale=6; 355/113" | bc
3.141592
Como se puede comprobar, esta división se aproxima a pi, pero el binomio bc/scale no redondean, únicamente cortan la muestra de decimales. Pero al menos muestran decimales, que let no lo hace.
Visto esto, modificamos el script del ejemplo:
#!/bin/bashlet operador1=3let operador2=2let suma=$operador1+$operador2let resta=$operador1-$operador2let multiplicacion=$operador1*$operador2division=$(echo "scale=2; $operador1/$operador2" | bc)let modulo=$operador1%$operador2echo "La suma de "$operador1" + "$operador2" es "$sumaecho "La diferencia de "$operador1" + "$operador2" es "$restaecho "El producto de "$operador1" x "$operador2" es "$multiplicacionecho "El cociente de "$operador1" / "$operador2" es "$divisionecho "El resto de "$operador1" / "$operador2" es "$modulo
Nótese que he quitado el let a la hora de definir la variable $division. Al ser decimal no lo acepta let.
El resultado que nos devuelve el script:
La suma de 3 + 2 es 5
La diferencia de 3 + 2 es 1
El producto de 3 x 2 es 6
El cociente de 3 / 2 es 1.50
El resto de 3 / 2 es 1
Esto ya es más bonito. Si decimos que pi es 3 exactamente que sea para llamar la atención, como el Profesor Frink, no porque nuestro script no sepa calcular con decimales.
Podemos descargar en nuestro ordenador todos los tuits de un usuario de Twitter pinchando uno a uno, dándole botón derecho y guardando la página... o haciendo un pequeño script.
Veamos paso a paso cómo hacerlo:
Acotar la búsqueda
Vamos a usar la cuenta de @LinuxCenterEs como ejemplo, así que vamos a mirar cuándo se unió a twitter, a qué fecha estamos y usaremos esos dos datos para delimitar el rango temporal desde donde hay que descargar.
Para saber la fecha actual, usaremos date.
Para saber la fecha en la que se unió un usuario a Twitter, tenemos que mirar en el código fuente. Para saber qué cadena buscamos, vamos a mirar primero la fecha en el perfil del usuario:

Después buscamos esa cadena en el código fuente y miramos a qué clase o div pertenece:

Y comprobamos si esa clase se utiliza únicamente para ese dato:

Ya tenemos la pista con la que extraer la cadena de la fecha en la que se unió el usuario a Twitter.
Ahora tenemos que descargar la página, buscar la línea y extraer únicamente la cadena:
#!/bin/bashwget https://twitter.com/$1 -O $1.htmllineafecha=$(cat $i.html | grep "ProfileHeaderCard-joinDateText js-tooltip u-dir")echo $lineafecha
Voy a comprobar si funciona hasta aquí:
Por ahora parece que vamos bien. Ahora vamos a limpiar la cadena.
Ojo, tenemos que limpiar acorde con el idioma de nuestro sistema. Si, por ejemplo, tuviésemos el sistema en inglés, la cadena es distinta:
Veamos cómo llevamos el script hasta ahora:
#!/bin/bashwget https://twitter.com/$1 -O $1.htmllineafecha=$(cat $i.html | grep "ProfileHeaderCard-joinDateText js-tooltip u-dir")mescomienzo=$(echo $lineafecha | cut -d " " -f 13)anocomienzo=$(echo $lineafecha | cut -d " " -f 15 | cut -d "<" -f 1)case $mescomienzo inenero) mescomienzo="01";;febrero) mescomienzo="02";;marzo) mescomienzo="03";;abril) mescomienzo="04";;mayo) mescomienzo="05";;junio) mescomienzo="06";;julio) mescomienzo="07";;agosto) mescomienzo="08";;septiembre) mescomienzo="09";;octubre) mescomienzo="10";;noviembre) mescomienzo="11";;diciembre) mescomienzo="12";;esacmesfinal=$(date +%m)anofinal=$(date +%Y)echo "Las fechas a buscar son desde el "$mescomienzo" de "$anocomienzo" hasta el "$mesfinal" de "$anofinal
Y veamos si funciona bien:
Pues parece que vamos por buen camino. Así que seguimos:
Ahora que ya sabemos las fechas que hemos de recorrer, vamos a ver cómo explotar este dato, tal como adelanté en un hilo de twitter: https://twitter.com/hacemoswebserie/status/1040037466802151426
Antes de seguir, comentaré que la cadena para hacer una búsqueda de los tuits que un determinado usuario de Twitter ha publicado entre dos fechas es:
"https://twitter.com/search?q=From%3A"$1"%20since%3A"$anocomienzo"-"$mescomienzo"-"$diacomienzo"%20until%3A"$anofinal"-"$mesfinal"-"$diafinal"&src=typd"
Y siempre es así. Simplemente cambiando las variables. Pero lo mejor de todo, es que todas las redes sociales siguen patrones similares, por lo que este tutorial es extrapolable a cualquier otra red social o cualquier otra búsqueda dentro de Twitter.
Y una advertencia, a veces si hacemos muchas descargas desde una misma IP o desde un mismo navegador, puede que nos bloquee ese servidor las descargas y no sirva nuestro script. Varias soluciones para esto:
Pero si vamos a hacer un uso moderado, como en este ejemplo en el que descargarnos los tuits de un único usuario, no debería darnos más problema que no nos deja descargar con wget si no le indicamos un "user agent" distinto.
Así que le indicaremos a wget que, por ejemplo, usamos Firefox con Windows. Lo haremos con: wget --user-agent="Mozilla/5.0 (Windows; U; Windows NT 5.1; pl; rv:1.9.2.3) Gecko/20100401 Lightningquail/3.6.3"
Y nos quedaría un script así:
#!/bin/bashlineafecha=$(cat $i.html | grep "ProfileHeaderCard-joinDateText js-tooltip u-dir")mescomienzo=$(echo $lineafecha | cut -d " " -f 13)anocomienzo=$(echo $lineafecha | cut -d " " -f 15 | cut -d "<" -f 1)case $mescomienzo inenero) mescomienzo="01";;febrero) mescomienzo="02";;marzo) mescomienzo="03";;abril) mescomienzo="04";;mayo) mescomienzo="05";;junio) mescomienzo="06";;julio) mescomienzo="07";;agosto) mescomienzo="08";;septiembre) mescomienzo="09";;octubre) mescomienzo="10";;noviembre) mescomienzo="11";;diciembre) mescomienzo="12";;esacmesfinal=$(date +%m)anofinal=$(date +%Y)for a in $(seq $anocomienzo $anofinal) do for m in $(seq 1 12) do if [ $m -lt 10 ] then m="0"$m fi cadena="https://twitter.com/search?q=From%3A"$1"%20since%3A"$a"-"$m"-01%20until%3A"$a"-"$m"-31&src=typd" wget --user-agent="Mozilla/5.0 (Windows; U; Windows NT 5.1; pl; rv:1.9.2.3) Gecko/20100401 Lightningquail/3.6.3" $cadena -O "tuits-"$m"-"$a".html" done done
Sobre esta base, podemos seguir evolucionando, como definir que el primer año sólo descargue a partir del mes de comienzo, que el último año sólo descargue hasta el mes de final, que descargue los tuits día a día... Eso a gusto de cada uno.
En cuanto a usos, una vez descargados todos los tuits, podemos analizar las candenas con múltiples fines, desde hacer análisis de mercados, comprobar el número de adjetivos, sustantivos, pronombres... que utiliza ese usuario, o, por ejemplo, saber cuántas veces nos cita ese usuario:
Veamos cómo se porta LinuxCenterEs conmigo:
#!/bin/bashlet contador=0for i in $(ls *.html)do let nominaciones=$(cat $i | grep -c hacemoswebserie) let contador=$contador+$nominaciones let nominaciones=0doneecho "Te han citado "$contador" veces."
Veamos si funciona:

Y como funciona todo hasta aquí, aquí dejo el artículo. Ahora es tarea de cada usuario el adaptar este artículo a sus intereses. Las bases están sentadas... que la evolución haga el resto.
Para trabajar con fechas y con horas en un script, o en la consola, tenemos el comando date. Veamos su uso y algunos de sus parámetros más usuales.
Si únicamente escribimos date, nos devuelve el día de la semana indicado con tres caracteres; el mes, indicado de nuevo con tres caracteres; la hora completa con hora, minutos y segundos; el huso horario de nuestro ordenador y el año actual.
Parámetros usuales:
date +%H -> indica la hora (sin minutos) actual
date +%M -> indica los minutos actuales
date +%H:%M -> indica la hora y minutos actuales
date +%d -> indica qué día del mes es hoy
date +%m -> indica el mes actual en número
date +%B -> indica el mes actual en letras, con la palabra completa
date +%Y -> indica el año actual
Ejemplo:
echo "Hoy es el "$(date +%D)" de "$(date +%B)" de "$(date +%y)
Puede que te preguntes si te tienes que aprender todas esas opciones para hacer un uso eficaz de date. No. No hace falta. GNU/Linux es un sistema que permite optimizar todos los recursos... incluída la memoria de los usuarios. Con un sencillo script, podemos indicarle a un array que incluya las letras mayúsculas, a otro que incluya las minúsculas, concatene ambos arrays y lo recorra devolviendo el restultado de pasarle esos parámetros a date.
Hecho esto, lo ejecutamos y vemos qué parámetros son los que más nos interesan... incluso vemos qué letras no son parámetros de date.
Aquí dejo el script:
#!/bin/bashminusculas=({a..z})mayusculas=({A..Z})letras=($(echo ${minusculas[*]}) $(echo ${mayusculas[*]}))for i in $(seq 0 ${#letras[*]})do echo "date +"${letras[$i]}"%: "$(date +%${letras[$i]})done
Ya hemos visto que, dado que %$valor lo que hace es calcular el resto de la división entre la cifra que precede a % (dividendo) y la cifra que sucede a % (divisor), $RANDOM%limitesuperior no vale para generar números aleatorios de forma fideligna (a no ser que seamos trileros, pero en este caso no buscamos una verdadera aleatoriedad).
También hemos visto que shuf sí que sirve para crear órdenes aleatorios. Y ahora vamos a ver cómo usar shuf para generar números aleatorios.
Veamos cómo usar shuf en el primer ejemplo que usé en contra de $RANDOM: una aleatoriedad dos valores. Pero antes de ello, vuelvo a remarcar que shuf no genera nada, sino que hace un orden aleatorio de una lista. Así que primero hay que generar una lista y luego ordenar aleatoriamente con shuf.
Veamos varias formas de hacerlo:
En un fichero auxiliar:
#/bin/bashlet comienzo=0let final=1for i in $(seq $comienzo $final)do echo $i >> numeros.txtdone
Y ahora podemos ejecutarlo y luego ordenar con shuf. Veamos a ver si la aleatoriedad es tal:
En este pantallazo vemos que de 12 tiradas, han salido siete veces 0 y cinco veces 1. Está dentro de la normalidad. Pero vamos a ver qué ocurre si tiramos mil veces la moneda:
#/bin/bashlet ceros=0let unos=0for i in $(seq 1 1000)do let moneda=$(shuf -n1 numeros.txt) if [ $moneda -eq 0 ] then let ceros++ else let unos++ fidoneecho "El número de ceros que ha salido es: "$cerosecho "El número de unos que ha salido es: "$unos
Y lo comprobamos (vuelvo a poner pantallazo para que se vea que no hay trampa ni cartón):

¡Incluso me ha llegado a dar un empate de 500 a 500!
Visto que shuf es más válido en este caso, vamos a ver cómo generar una lista aleatoria con shuf. Compruebo en el mismo script para evitar hacer un artículo excesivamente largo:
#/bin/bashlet ceros=0let unos=0for i in $(seq 1 1000)do numeros=$(shuf -i 0-1) if [[ ${numeros:0:1} == "0" ]] then let ceros++ else let unos++ fidoneecho "El número de ceros que ha salido es: "$cerosecho "El número de unos que ha salido es: "$unos

Funciona... y de nuevo hemos tenido un empate a 500 en una tirada.
Pero ahora te puedes preguntar, por qué si existe el parámetro -i en shuf para generar una lista con orden aleatorio y el parámetro -n para extraer un número determinado de elementos, ¿por qué no lo usas en la misma línea?
No te falta razón para pensarlo, pero quería mostrar distintas formas de lanzar la moneda. Y hacer un artículo evolutivo que acabase con la mejor opción:
#/bin/bashlet ceros=0let unos=0for i in $(seq 1 1000)do numeros=$(shuf -i 0-1 -n 1) if [[ $numeros == "0" ]] then let ceros++ else let unos++ fidoneecho "El número de ceros que ha salido es: "$cerosecho "El número de unos que ha salido es: "$unos

Y vuelve a funcionar... incluso me ha dado otro empate a 500.
¿Y si en lugar de tirar a cara o cruz hacemos un rango mayor, por ejemplo de 0 a 1000?
Simplemente le cambiamos el rango en -i. Y si además, para comprobar si funciona, vamos a aumentar el número de tiradas.
#/bin/bashlet ceros=0let quinientos=0let miles=0for i in $(seq 1 10000)do numeros=$(shuf -i 0-1000 -n 1) if [[ $numeros == "0" ]] then let ceros++ elif [[ $numeros == "500" ]] then let quinientos++ elif [[ $numeros == "1000" ]] then let miles++ fidoneecho "El número de ceros que ha salido es: "$cerosecho "El número de quinientos que ha salido es: "$quinientosecho "El número de miles que ha salido es: "$miles
Y al probarlo, da unos resultados tales como:

Que son unos resultados que son totalmente coherentes dentro de lo que es la aleatoriedad por lo que podemos decir que la mejor forma para generar un número aleatorio en Bash es
shuf -i $limiteinferior-$limitesuperior -n 1
En tres artículos he estado argumentando en contra del uso de $RANDOM para generar números aleatorios:
El mito de $((RANDOM%$limitesuperior))
Más sobre la "aleatoriedad" de $RANDOM
Dados cargados en $RANDOM
Pero es poco honesto criticar algo sin aportar una solución, así que aquí voy a explicar el uso de shuf y en otro artículo explicaré varias formas de usar shuf para generar números aleatorios.
shuf se puede usar para ordenar aleatoriamente un fichero de texto:
shuf fichero.txt

Como vemos al usar cat, las palabras de fichero.txt tenían un orden que, al usar shuf, han cambiado de orden.
También shuf permite seleccionar X elementos de una lista. Ya lo hemos usado otras veces, como al elegir aleatoriamente una tipografía o un color con ImageMagick. Pero veamos con el ejemplo anterior:
shuf -n2 fichero
Con el parámetro -n$numerodelineas muestra el número de líneas incado como valor a -n.
Otra forma de usar shuf es recibiendo una lista desde la salida de otro fichero.
Veamos un ejemplo creando una serie de ficheros en un directorio y luego seleccionando uno al azar recogiendo con shuf la salida de ls.
Otro parámetro muy interesante es -o para especificar un fichero donde guardar la salida de shuf. Evolucionemos la instrucción anterior:
ls | shuf -o ordenaleatorio.txt
El pasado sábado 08 de Septiembre se realizó en nuestras instalaciones el curso "BASH: El poder de la terminal y el scripting".
En el curso aprendimos a realizar:
- Personalizacion de la terminal con bash
- Scripts que no requieran interaccion
- CLI
- GUI
- Uso del comando Sed
Bash (Bourne-again shell) es un programa informático, cuya función consiste en interpretar órdenes, y un lenguaje de consola. Es una shell de Unix compatible con POSIX y el intérprete de comandos por defecto en la mayoría de las distribuciones GNU/Linux, además de macOS. También se ha llevado a otros sistemas como Windows y Android. (+info sobre BASH wikipedia)
Aquí os dejamos el vídeo del curso completo:
Raúl Rodrigo, desarrollador oficial de la distribucion LliureX desde 2009 con alguna que otra incursion a la empresa privada, llevo entregado toda mi vida profesional al software libre. Desarrollo en python sobre ubuntu por profesion y en JS y sobre Arch linux por aficion.