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]
Publicado en Programación

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/bash
if [ "$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"
    fi
fi

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"
    fi
fi

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"
    fi
fi

Que devuelve:

La variable no existe

Publicado en Programación
Jueves, 01 Noviembre 2018 21:09

División con decimales en Bash

Para hacer operaciones sencillas en Bash podemos usar let, pero únicamente operaciones muy sencillas. Veamos un ejemplo:

#!/bin/bash

let operador1=3
let operador2=2

let suma=$operador1+$operador2
let resta=$operador1-$operador2
let multiplicacion=$operador1*$operador2
let division=$operador1/$operador2
let modulo=$operador1%$operador2


echo "La suma de "$operador1" + "$operador2" es "$suma
echo "La diferencia de "$operador1" + "$operador2" es "$resta
echo "El producto de "$operador1" x "$operador2" es "$multiplicacion
echo "El cociente de "$operador1" / "$operador2" es "$division
echo "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/bash

let operador1=3
let operador2=2

let suma=$operador1+$operador2
let resta=$operador1-$operador2
let multiplicacion=$operador1*$operador2
division=$(echo "scale=2; $operador1/$operador2" | bc)
let modulo=$operador1%$operador2

echo "La suma de "$operador1" + "$operador2" es "$suma
echo "La diferencia de "$operador1" + "$operador2" es "$resta
echo "El producto de "$operador1" x "$operador2" es "$multiplicacion
echo "El cociente de "$operador1" / "$operador2" es "$division
echo "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.

 

 

 

Publicado en Programación

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:

fechatwitter.png

 

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

clasetwitter.png

 

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

unacierto.png

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/bash

wget https://twitter.com/$1 -O $1.html

lineafecha=$(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/bash

wget https://twitter.com/$1 -O $1.html

lineafecha=$(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 in
enero)
    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"
;;
esac


mesfinal=$(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:

  • Usar Tor para usar distintas IPs.
  • Lanzar varios scripts desde distintos ordenadores con distintas IPs (mejor si usamos servidores desde distintos puntos del planeta).
  • Ir cambiando de "user agent", es decir, cambiar la cabecera de wget para que se haga pasar por otro navegador. Tenemos un enorme listado de cabeceras en la página http://www.useragentstring.com/

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/bash

lineafecha=$(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 in
enero)
    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"
;;
esac


mesfinal=$(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/bash

let contador=0

for i in $(ls *.html)
do
    let nominaciones=$(cat $i | grep -c hacemoswebserie)
    let contador=$contador+$nominaciones
    let nominaciones=0
done

echo "Te han citado "$contador" veces."

 

Veamos si funciona:

nominaciones.png

 

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.

 

Publicado en Programación
Jueves, 13 Septiembre 2018 17:08

Trabajar con fechas en Bash

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)

Truco para usar date

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/bash

minusculas=({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

Publicado en Programación
Miércoles, 12 Septiembre 2018 20:31

Generar números aleatoriamente con shuf

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.

Lanzamiento a cara o cruz de una moneda (aleatoriedad entre dos valores)

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.

¿Qué formas podemos generar una lista?

Veamos varias formas de hacerlo:

En un fichero auxiliar:

#/bin/bash

let comienzo=0
let final=1

for i in $(seq $comienzo $final)
do
    echo $i >> numeros.txt
done

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/bash

let ceros=0
let unos=0

for i in $(seq 1 1000)
do
    let moneda=$(shuf -n1 numeros.txt)
    if [ $moneda -eq 0 ]
    then
        let ceros++
    else
        let unos++
    fi
done

echo "El número de ceros que ha salido es: "$ceros
echo "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):

comprobacionconfichero.png

 

¡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/bash

let ceros=0
let unos=0

for i in $(seq 1 1000)
do
    numeros=$(shuf -i 0-1)    
    if [[ ${numeros:0:1} == "0" ]]
    then
        let ceros++
    else
        let unos++
    fi
done

echo "El número de ceros que ha salido es: "$ceros
echo "El número de unos que ha salido es: "$unos

listaconshuf.png

 

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/bash

let ceros=0
let unos=0

for i in $(seq 1 1000)
do
    numeros=$(shuf -i 0-1 -n 1)    
    if [[ $numeros == "0" ]]
    then
        let ceros++
    else
        let unos++
    fi
done

echo "El número de ceros que ha salido es: "$ceros
echo "El número de unos que ha salido es: "$unos

listaconshufin.png

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/bash

let ceros=0
let quinientos=0
let miles=0

for 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++
    fi
done

echo "El número de ceros que ha salido es: "$ceros
echo "El número de quinientos que ha salido es: "$quinientos
echo "El número de miles que ha salido es: "$miles

Y al probarlo, da unos resultados tales como:

 

 

delunoalmil.png

 

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

Publicado en Programación
Miércoles, 12 Septiembre 2018 00:02

Aleatoriedad con shuf

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.

El uso de shuf

shuf se puede usar para ordenar aleatoriamente un fichero de texto:

shuf fichero.txt

shuf.png

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

Publicado en Programación
Miércoles, 29 Agosto 2018 22:16

Función para numerar fotogramas

Ahora que sabemos editar un vídeo a partir de una secuencia de fotogramas con ffmpeg nos encontramos con que para poder hacerlo, los fotogramas han de estar numerados correlativamente y todos con el mismo número de dígitos en el numeral para que los ordene bien en el vídeo, lo cual es sencillo, pero si lo automatizamos, mejor.

Aquí os dejo la función que utilizo para ello:

function devuelvefotograma()
{

# Función que recibe el numeral del último fotograma creado y devuelve el fotograma a utilizar como imagen destino por convert


    esentero='^[0-9]+$'
    numerofotograma=$1
    
    if [ -z $numerofotograma ] || ! [[ $numerofotograma =~ $esentero ]]
    then
        let numerofotograma=1
    fi
    

    if [ $numerofotograma -lt 10 ]
    then
        fotograma="fotograma000"$numerofotograma".png"
    
    elif [ $numerofotograma -ge 10 ] && [ $numerofotograma -lt 100 ]
    then
        fotograma="fotograma00"$numerofotograma".png"
    elif [ $numerofotograma -ge 100 ] && [ $numerofotograma -lt 1000 ]
    then
        fotograma="fotograma0"$numerofotograma".png"
    else
        fotograma="fotograma"$numerofotograma".png"
    fi

    echo $fotograma;
}

 

Explicación de la función

 

Primero defino la función y le llamo devuelvefotograma, ya que me gusta que el nombre de una función sea descriptivo, así que suelo usar un imperativo que indique lo que quiero que haga.
Una descripción de qué va a hacer la función. 
Y comienzo la función diciéndole que $numerofotograma (también un nombre que deje claro qué va a almacenar esa variable) tome el valor de $1, es decir, del primer parámetro que le pasamos a la función. Y no es que no me guste usar el nombre de la variable $1, sino que $1 es genérico para todas las funciones y $numerofotograma es más sencillo de entender (sí, hay que teclear un poco más, pero a la hora de mantener un script es mejor si el nombre de las variables indican qué contienen. Y si tienes configurado en tu editor de textos que al pulsar tabulador complete los nombres de las funciones, tampoco tienes que teclear mucho más).
Le indico también que la variable $esentero sea una expresión regular que diga que desde el comienzo hasta el final sean números ('^[0-9]+$').

Compruebo si no tiene contenido en la variable ([ -z $numerofotograma ]) o (||) no es numérico (! [[ $numerofotograma =~ $esentero ]]) y si no tiene nada la variable o no es numérica, le soy el valor 1. Ya sé que puede sobreescribir fotogramas al hacerlo así, pero prefiero darle un valor a la variable que me de error la función. A gustos.

Pregunto si $numerofotograma es menor que 10 con [ $numerofotograma -lt 10 ], es decir, si únicamente tiene un carácter. Si es así, le añado tres ceros. Por ejemplo, si el número del fotograma es 1, le digo que sea 0001.

Si es igual o mayor que 10 ([ $numerofotograma -ge 10 ]) y además (&&), es menor que cien ([ $numerofotograma -lt 100 ]) es que tiene dos carácteres, así que le añado dos ceros. Si tiene tres carácteres es que va de 100 a 999, así que le digo que si es igual o mayor que cien y menor que mil, que añada un 0.

Y si es mayor que 1000 es que tiene 4 dígitos o más. Y no añado ningún cero. Alguien podría decir que qué pasa si hay más de 9.999 fotogramas. Sencillamente, que no uso esta función, ya que usaría la opción de codificar el vídeo al vuelo. Esta función la uso para generar y ver los fotogramas antes de renderizar un vídeo. Si un vídeo tiene más de 9.999 fotogramas puede ocupar unos 70 Gb de espacio en disco. Y no voy a visualizar individualmente tantos fotogramas.
La opción de visualizar fotograma a fotograma es para secuencias cortas en las que hay detalles importantes o una muestra para ver cómo quedan los fotogramas antes de hacer el vídeo.

Por último, devuelvo $fotograma.

Variables e invocación de la función

Podemos definir la variable $numerofotograma o no. Si no la inciamos, la función la inicializa a 1. Si la queremos inicializar, por hacer un script más elegante, lo podemos hacer con:

let numerofotograma=1

Invocamos a la función pasándole el número de fotograma como parámetro y nada más saber el fotograma a utilizar, le decimos que le sume 1. Podemos hacerlo más tarde, pero es más fácil que se nos olvide si lo dejamos para después.

fotograma=$(devuelvefotograma $numerofotograma)
let numerofotograma++

Y ya podemos usar la variable fotograma como imagen de destino con convert.

 

La función en un script

 

#!/bin/bash

function devuelvefotograma()
{

# Función que recibe el numeral del último fotograma creado y devuelve el fotograma a utilizar como imagen destino por convert


    esentero='^[0-9]+$'
    numerofotograma=$1
    
    if [ -z $numerofotograma ] || ! [[ $numerofotograma =~ $esentero ]]
    then
        let numerofotograma=1
    fi
    

    if [ $numerofotograma -lt 10 ]
    then
        fotograma="fotograma000"$numerofotograma".png"
    
    elif [ $numerofotograma -ge 10 ] && [ $numerofotograma -lt 100 ]
    then
        fotograma="fotograma00"$numerofotograma".png"
    elif [ $numerofotograma -ge 100 ] && [ $numerofotograma -lt 1000 ]
    then
        fotograma="fotograma0"$numerofotograma".png"
    else
        fotograma="fotograma"$numerofotograma".png"
    fi

    echo $fotograma;
}

for i in $(seq 1 50)
do
    fotograma=$(devuelvefotograma $numerofotograma)
    let numerofotograma++
    echo "El fotograma es: "$fotograma
done

 

Este es un script de control, para ver que devuelve bien el nombre del fotograma. Usa cualquier parámetro de convert para generar los fotogramas con las modificaciones que desees.

Publicado en Programación
Sábado, 25 Agosto 2018 11:25

Dados cargados en $RANDOM

Ya hemos visto que $RANDOM%$limitesuperior no funciona.

Hemos visto que $RANDOM%1 para hacer tiradas "a cara o cruz" no funciona.

Y del mismo modo que deberemos sospechar (y no apostar) si vemos que un trilero siempre saca el mismo resultado lanzando una moneda, cuando lanza un dado puede que haga también trampa. Los dados se pueden cargar y hacer que un lado pese más, por lo que hay más posibilidades de que ese lado sea el que se apoye en la mesa y, por lo tanto, sea el lado apostado el que quede arriba. Pasa lo mismo con $RANDOM:

#!/bin/bash

let nocoincide=0

for i in $(seq 1 1000)
do
    for x in $(seq 1 1000)
    do
       let numero=$((RANDOM%$x))
       if [ $numero -lt 10 ]
       then
            echo $numero >> coincidencias.txt
       fi
    
    done
done

for i in $(seq 0 9)
do
    echo "Veces que se repite "$i
    fgrep -o $i coincidencias.txt | wc -l
done

 

El resultado que me ha dado es (cada vez que lo probemos saldŕa algo distinto, ya que son números aleatorios):

Veces que se repite 0
7559
Veces que se repite 1
6668
Veces que se repite 2
5941
Veces que se repite 3
5730
Veces que se repite 4
5387
Veces que se repite 5
5219
Veces que se repite 6
5053
Veces que se repite 7
4810
Veces que se repite 8
4769
Veces que se repite 9
4638

 

Cuanto más pequeño es el número, más veces se repite. Curioso caso de "aleatoriedad". ¿No os resulta sospechoso? A mi, sí. Por lo que voy a compararlo con otros números. Y antes, voy a borrar coincidencias.txt para evitar viciar resultados:

rm coincidencias.txt

Y ahora, el script:

#!/bin/bash

let nocoincide=0

for i in $(seq 1 1000)
do
    for x in $(seq 1 1000)
    do
       let numero=$((RANDOM%$x))
       if [ $numero -lt 10 ]
       then
            echo $numero >> coincidencias.txt
       elif [ $numero -eq 100 ]
       then
            echo $numero >> coincidencias.txt
       elif [ $numero -eq 500 ]
       then
            echo $numero >> coincidencias.txt
       elif [ $numero -eq 750 ]
       then
            echo $numero >> coincidencias.txt
       elif [ $numero -eq 999 ]
       then
            echo $numero >> coincidencias.txt
       fi
    
    done
done

for i in $(seq 0 9)
do
    echo "Veces que se repite "$i":"
    fgrep -o $i coincidencias.txt | wc -l
done

echo "Datos de control"
for i in 100 500 750 999
do
    echo "Veces que se repite "$i":"
    fgrep -o $i coincidencias.txt | wc -l
done

Y el resultado:

Veces que se repite 0:
13717
Veces que se repite 1:
8694
Veces que se repite 2:
5986
Veces que se repite 3:
5651
Veces que se repite 4:
5389
Veces que se repite 5:
6259
Veces que se repite 6:
5029
Veces que se repite 7:
5339
Veces que se repite 8:
4796
Veces que se repite 9:
4701
Datos de control
Veces que se repite 100:
2296
Veces que se repite 500:
687
Veces que se repite 750:
277
Veces que se repite 999:
0

 

Ummmm... Como digo, algo que es aleatorio puede cambiar el resultado. Pero si siempre que ejecutamos el script nos da el mismo patrón, que es que cuanto más pequeño sea el número, más veces sale, es que esa aleatoriedad está tan viciada con los dados cargados de un trilero. Y es que $numero en $RANDOM%$numero no indica el límite superior sino el divisor de un dividendo aleatorio en una instrucción en la que le pedimos que nos indique el resto.

Ni más, ni menos.

Publicado en Programación
Sábado, 25 Agosto 2018 10:57

Más sobre la aleatoriedad de $RANDOM

En otro artículo ya comenté que para hacer una especie de "cara o cruz" en el que de forma aleatoria nos muestre 0 o 1, $RANDOM%1 no funciona, ya que siempre da 0. En un comentario me decían que dividir una cantidad entre 1 no da decimales. Es cierto, nunca podrá haber rendondeo a la alza porque nunca podrá haber redondeo y, por lo tanto, nunca podrá salir la cantidad indicada como límite superior del rango.

Voy a hacer un script en el que saquemos muchos números aleatorios. Por ejemplo, que mil veces haga una tirada aleatoria con resultados del 0 al 1.000. 

#!/bin/bash

for i in $(seq 1 1000)
do
    for x in $(seq 1 1000)
    do
       let numero=$((RANDOM%$x))
       if [ $numero -eq $x ]
       then
            echo $x
       fi
    done
done

Al ejecutarlo no sale nada. Y repito, y repito, y repito la ejecución del script. Siempre sin ningún resultado. ¿Me habré equivocado? Voy a acumular las veces que el resultado no coincide con el límite superior:

#!/bin/bash

let nocoincide=0

for i in $(seq 1 1000)
do
    for x in $(seq 1 1000)
    do
       let numero=$((RANDOM%$x))
       if [ $numero -eq $x ]
       then
            echo $x
       else
            let nocoincide++
       fi
    
    done
done

echo "Han salido "$nocoincide" coincidencias con el límite superior"

Ahora el resultado siempre es el mismo:

Han salido 1000000 coincidencias con el límite superior

Pues igual no he sido yo el que me he equivocado. Aunque es cierto que al ser aleatorio puede que ese número no salga. Pero si varias veces repito un millón de tiradas y nunca coincide, algo raro pasa. Quizá lo raro es que quien afirma que para sacar un número aleatorio entre 0 y $x se puede conseguir con let numero=$((RANDOM%$x)) se equivoca... o que nunca se ha parado a comprobarlo.

Publicado en Programación

¡Atención! Este sitio usa cookies y tecnologías similares.

Si no cambia la configuración de su navegador, usted acepta su uso. Saber más

Acepto

Vea nuestra política de cookies y enlaces de interés aquí