Bash nos permite hacer millones de cosas. Es más, como nos permite hacer llamadas a otros programas, colocando las entradas y salidas adecuadas podremos producir cualquier resultado. Nos viene muy bien poder encadenar varios comandos, como en este tutorial en el que la salida de echo la pasamos como entrada de tr. Pero en este tutorial quiero hablar de operaciones que podemos hacer directamente en Bash, sin ningún programa extra o dependencia. Además, su realización será mucho más rápida ya que no tenemos que cargar en memoria un programa. Esto nos resultará de gran ayuda si utilizamos sistemas empotrados como Raspberry PI, o cualquier dispositivo con chips o almacenamiento reducidos.
Eso sí, para muchos ejemplos necesitaremos Bash 4.x (aunque ya tiene unos años, la versión salió en 2009)
¡Manos a la obra!
Aunque existen herramientas como sed o awk que, no estoy diciendo que no las usemos, porque son herramientas muy potentes. Cuando se trata de hacer un simple reemplazo de un texto por otro dentro de un texto más grande. O lo que es lo mismo, reemplazar subcadenas dentro de una cadena, podemos hacer lo siguiente:
texto="Los mejores tutoriales sobre Windows en WindowsCenter" echo ${texto//Windows/Linux}
La clave está en tener el texto en una variable y luego hacer
echo ${VARIABLE//Cadena a buscar/Cadena que reemplaza}
O también podemos hacer lo siguiente:
texto="No hables de Linux en LinuxCenter" echo ${texto/Linux/Windows}
Fijaos que ahora solo hay una barra (/) en lugar de dos. Con las sustituciones podemos hacer muchas cosas, incluso utilizar expresiones regulares.
Es muy común hacer lo siguiente:
texto="cadena de caracteres muy muy larga" echo $texto | wc -m
Pero podemos hacerlo sin llamar a wc (cuidado, también podemos utilizar -c, pero tendremos problemas con caracteres especiales) de la siguiente manera:
texto="cadena de caracteres muy muy larga" echo ${#texto}
Una nota, si comparáis los dos resultados, saldrán distintos, el primer método tendrá un carácter más. Eso es porque contamos el "intro" o mejor dicho, el retorno de carro, cuando utilizamos la primera forma. También podríamos hacerlo así:
texto="cadena de caracteres muy muy larga" echo -n $texto | wc -m
¿Qué ocurre si queremos sacar una subcadena dentro del texto? Por ejemplo, tenemos esta cadena de texto:"Los mejores tutoriales sobre Linux en LinuxCenter"
Y queremos extraer 10 letras desde el carácter número 12, podemos hacer:
texto="Los mejores tutoriales sobre Linux en LinuxCenter" echo ${texto:12:10}
O si queremos sacar desde el carácter 37 hasta el final, podemos hacer:
texto="Los mejores tutoriales sobre Linux en LinuxCenter" echo ${texto:37}
Si tengo este texto: "Esto es un pequeño tutorial de Linux en el que hablo de Bash."
Y quiero saber si una subcadena está presente, puedo hacer lo siguiente:
texto="Esto es un pequeño tutorial de Linux en el que hablo de Bash." if [[ "$texto" == *"Windows"* ]]; then echo "ESTÁ"; else echo "NO ESTÁ"; fi
Si tenemos un nombre de archivo con la ruta completa y queremos extraer el directorio donde está localizado el fichero podemos hacer lo siguiente:
fichero="/usr/share/icons/hicolor/64x64/mimetypes/libreoffice-oasis-data.png" echo "${fichero%/*}"
En este caso obtendremos: /usr/share/icons/hicolor/64x64/mimetypes/ . En teoría estamos eliminando de la cadena desde el final hasta la última / y devolvemos lo que nos queda. Al final obtenemos lo mismo que haciendo:
dirname /usr/share/icons/hicolor/64x64/mimetypes/libreoffice-oasis-data.png
Ahora si lo que queremos es sacar el nombre de archivo libreoffice-oasis-data.png sin la ruta, igual que hacemos con el comando basename. Podemos hacer:
fichero="/usr/share/icons/hicolor/64x64/mimetypes/libreoffice-oasis-data.png" echo "${fichero##*/}"
En este caso, eliminamos todos los caracteres desde el principio hasta la última / que encontramos.
De la misma forma que hicimos para sacar la ruta del archivo, vamos a extraer la extensión de un archivo, de la siguiente manera:
fichero="/home/usuario/backups/2018_11_13.tar.gz" echo "${fichero%%.*}"
Ahora queremos quedarnos con la extensión, para ello, propongo dos casos. En el primero, sacaremos todo lo que hay detrás del primer punto del archivo:
fichero="/home/usuario/backups/2018_11_13.tar.gz" echo ${fichero##*.}
Aunque también puede ser que solo necesitemos la última extensión (desde el último punto hasta el final):
fichero="/home/usuario/backups/2018_11_13.tar.gz" echo ${fichero#*.}
Queremos saber en qué letra se encuentra la palabra Linux dentro de la frase "Esto es un pequeño tutorial de Linux en el que hablo de Bash." así que hacemos lo siguiente:
texto="Esto es un pequeño tutorial de Linux en el que hablo de Bash." tmp="${texto%%Linux*}" echo ${#tmp}
Si os fijáis, es parecido a lo que hacíamos para extraer el nombre de archivo (solo que ahora en lugar de un punto, buscamos la palabra Linux, y luego contamos las letras de la cadena temporal generada. Incluso la palabra a buscar puede ser otra variable. Eso sí, tendremos un problema cuando la palabra no exista, que nos devolverá la posición del final de la cadena, entonces podemos hacer lo siguiente:
busca="Linux"texto="Esto es un pequeño tutorial de Linux en el que hablo de Bash." tmp="${texto%%$busca*}" [[ "$texto" = "$tmp" ]] && echo -1 || echo ${#tmp}
Con lo que si el texto no se encuentra, devuelve -1
Por último, quiero dejaros para copiar y pegar todo esto en forma de funciones para poder incorporar fácilmente a vuestros scripts estas utilidades:
#!/bin/bash function replace() { local text="$1" local busc="$2" local repl="$3" echo ${text//$busc/$repl} } function replace_once() { local text="$1" local busc="$2" local repl="$3" echo ${text/$busc/$repl} } function longitud() { local text="$1" echo ${#text} } function recorta() { local text="$1" local desde=$2 local hasta=$3 [ -z $hasta ] && echo ${text:$desde} || echo ${text:$desde:$hasta} } function encuentra() { local text="$1" local subt="$2" [[ "$texto" == *"Windows"* ]] && true || false } function ruta_archivo() { local fich="$1" echo "${fich%/*}" } function base_archivo() { local fich="$1" echo "${fich##*/}" } function extension_archivo() { local fich="$1" echo ${fich##*.} } function extension_archivo2() { local fich="$1" echo ${fich#*.} } function posicion_cadena() { local text="$1" local subc="$2" tmp="${text%%$subc*}" [[ "$texto" = "$tmp" ]] && echo -1 || echo ${#tmp} }
Ahora Probamos las funciones
replace "I love Windows, forever Windows" "Windows" "Linux"replace "I love Windows, forever Windows" "Windows" "Linux" replace_once "I love Windows. Windows never closes unexpectedly" "Windows" "Linux" longitud "Esta es una cadena muy larga que no se cuantas letras contiene" recorta "Desde que uso GNU/Linux soy más feliz" 14 9 recorta "Desde que uso GNU/Linux soy más feliz" 24 if encuentra "$(uname)" "Linux"; then echo "Buen sistema el tuyo!"; fi ruta_archivo "/home/usuario/proyectos/github/tutoriales/cadenas_bash.txt" base_archivo "/home/usuario/proyectos/github/tutoriales/cadenas_bash.txt" extension_archivo "/home/usuario/proyectos/github/tutoriales/cadenas_bash.tar.bz2" extension_archivo2 "/home/usuario/proyectos/github/tutoriales/cadenas_bash.tar.bz2" posicion_cadena "No sé dónde está Linux en todo este texto" "Linux"
¡Espero que les haya sido útil!
Uno de los primeros artículos que escribí en Linux Center sobre ImageMagick versaba sobre cómo generar lienzos simples en ImageMagick.
Ahora, después de haber publicado unos cuantos artículos más sobre ImageMagick me parece un buen momento para hacer una recopilación de las distintas formas de hacer un lienzo y cómo aplicarle efectos para poder generar multitud de imágenes distintas como fondo de escritorio, fondos para escribir textos en redes sociales y llamar así más la atención de los destinatarios, imágenes para WhatsAPP / Telegram... y de paso, ver también cómo construir instrucciones complejas en ImageMagick.
Para generar imágenes con ImageMagick el comando a usar es convert, al cual le tenemos que indicar el tamaño de la imagen con -size, el color a usar con xc: y el nombre de la imagen a generar.
La estructura más sencilla para generar una imagen con ImageMagick sería algo así:
convert -size 500x100 xc:Black franjanegra.png
Podríamos combinar en la misma instrucción varias veces el parámetro xc: con atención a que si no añadimos más parámetros, convert generará tantos ficheros como imágenes generadas con xc:
convert -size 500x100 xc:Black xc:Red xc:Blue franjanegra.png
Generará tres ficheros:
franjanegra-0.png
franjanegra-1.png
franjanegra-2.png
Si queremos que esos lienzos formen parte de un único fichero unido verticalmente necesitamos usar el parámetro append. Con -append la unión será vertical y con +append la unión será horzontal.
convert -size 500x100 xc:Black xc:Red xc:Blue -append tresfranjas.png
Como hemos visto hasta aquí, si únicamente utilizamos un -size, todas las piezas medirán lo mismo. Veamos cómo hacer una imagen con piezas de distintos tamaños. Y de paso, veamos cómo concatenar imágenes en formato horizontal (con +append):
convert -size 100x500 xc:Black -size 200x500 xc:Red -size 300x500 xc:Blue +append tresfranjasverticales.png
A los lienzos generados se les puede añadir nuevos parámetros. Por ejemplo -border que añade un borde al lienzo del color indicado con -bordercolor:
convert -size 300x300 xc:Red -bordercolor Black -border 100x100 negroborderojo.png
Los gradientes de color o degradados básicos que se pueden hacer en ImageMagick son lineales o radiales, con los parámetros gradient: y radial-gradient: en lugar de xc:
convert -size 500x500 gradient:Red-Blue degradadorojoazul.png
convert -size 500x500 radial-gradient:Red-Blue degradadoradialrojoazul.png
Una vez visto este recordatorio, aquí empezamos con la elaboración de instrucciones complejas en ImageMagick.
Los degradados lineales realizados con gradient no permiten grandes posibildades de adaptación, pero podemos unir parámetros, como xc: y gradient: para poder hacer imágenes en el que haya partes sin degradados:
convert -size 500x100 xc:Red -size 500x300 gradient:Red-Blue -size 500x100 xc:Blue -append unionxcgradient.png
Vamos a rizar un poco más el rizo y vamos a unir un parámetro más, el parámetro rotate para girar la imagen y que el degradado tenga formato horizontal:
convert -size 500x100 xc:Red -size 500x300 gradient:Red-Blue -size 500x100 xc:Blue -append -rotate 90 unionxcgradientgirado.png
Podemos encadenar distintos gradientes y lienzos lisos, tantos como queramos, aquí uniremos dos degradados con un lienzo liso a modo de ejemplo:
convert -size 500x200 gradient:Red-Blue -size 500x100 xc:Blue -size 500x200 gradient:Blue-Red -append doblegradiente.png
A los lienzos creados, podemos aplicarles un difuminado posterior para que haga el efecto de degradado
convert -size 500x250 xc:Blue xc:Red -append -blur 0x250 difuminadoblur.png
Y aquí viene un punto muy importante que quiero remarcar:
ImageMagick procesa los parámetros de izquierda a derecha, así que para que pueda generarse el difuminado en la unión de los dos lienzos es necesario que el parámetro -append esté antes del parámetro -blur.
A convert se le puede indicar que haga degradados en direcciones concretas. Tanto lineales como radiales. Veamos esta última opción con distintos grados aplicados como valor al parámetro -radial-blur ya que es más sencillo ver el resultado en una imagen que leer su descripción:
convert -size 500x250 xc:Blue xc:Red -append -radial-blur 45 difuminadoradialblur-45.png
convert -size 500x250 xc:Blue xc:Red -append -radial-blur 90 difuminadoradialblur-90.png
convert -size 500x250 xc:Blue xc:Red -append -radial-blur 135 difuminadoradialblur-135.png
convert -size 500x250 xc:Blue xc:Red -append -radial-blur 180 difuminadoradialblur-180.png
convert -size 500x250 xc:Blue xc:Red -append -radial-blur 225 difuminadoradialblur-225.png
convert -size 500x250 xc:Blue xc:Red -append -radial-blur 270 difuminadoradialblur-270.png
convert -size 500x250 xc:Blue xc:Red -append -radial-blur 315 difuminadoradialblur-315.png
convert -size 500x250 xc:Blue xc:Red -append -radial-blur 360 difuminadoradialblur-360.png
Vista la evolución de -radial-blur, veamos lo mismo con -motion-blur, que cuenta con tres parámetros: $radioX$sigma+$ángulo
convert -size 500x250 xc:Blue xc:Red -append -motion-blur 0x100+45 difuminadomotionblur-45.png
convert -size 500x250 xc:Blue xc:Red -append -motion-blur 0x100+90 difuminadomotionblur-90.png
convert -size 500x250 xc:Blue xc:Red -append -motion-blur 0x100+135 difuminadomotionblur-135.png
convert -size 500x250 xc:Blue xc:Red -append -motion-blur 0x100+180 difuminadomotionblur-180.png
convert -size 500x250 xc:Blue xc:Red -append -motion-blur 0x100+225 difuminadomotionblur-225.png
convert -size 500x250 xc:Blue xc:Red -append -motion-blur 0x100+270 difuminadomotionblur-270.png
convert -size 500x250 xc:Blue xc:Red -append -motion-blur 0x100+315 difuminadomotionblur-315.png
convert -size 500x250 xc:Blue xc:Red -append -motion-blur 0x100+360 difuminadomotionblur-360.png
Sobre todo quiero que se note cómo afecta a la imagen un -motion-blur con un ángulo de 180 ó 360 grados en una imagen con dos franjas horizontales. Y ahora veamos cómo afecta el valor del ángulo tanto en -radial-blur como -motion-blur en una imagen con predominancia de un color y una forma que la cruce. Por ejemplo, una cruz de San Jorge:
convert -size 400x400 xc:White -background Red -splice 100x100+200+200 sanjorge.png
Y ahora vamos a hacer una evolución de un -motion-blur y un -radial-blur para ver cómo afecta cuando la imagen muestra una forma de cruz:
Veamos la diferencia entre -motion-blur 90 y -motion-blur 180
Y entre -motion-blur 45 y -motion-blur 135
Y en cuanto a -radial-blur, veamos como afecta con 90 y múltiplos de 90: 180, 270 y 360:
Y sin embargo, cómo afecta cuando no son mútliplos de 90:
-radial-blur 45
-radial-blur 135
-radial-blur 225
-radial-blur 315
Otra forma de modificar las imágenes y hacer menos nítidos los bordes es mediantes las dispersiones con el parámetro -spread:
convert -size 500x250 xc:Red xc:Blue -append -spread 5 rojoazul-spread5.png
[code]convert -size 500x250 xc:Red xc:Blue -append -spread 50 rojoazul-spread50.png[/code]
Incluso se puede aplicar un valor mayor al tamaño en píxeles de la altura de la imagen dando un efecto de caos total:
convet -size 500x250 xc:Red xc:Blue -append -spread 1000 rojoazul-spread1000.png
En una imagen en el que no están definidas las líneas, sí que afecta el -motion-blur con 180 y 360:
[code]convert -size 500x250 xc:Red xc:Blue -append -spread 1000 -motion-blur 0x100+180 rojoazul-spread1000-motion-blur180.png[/code]
convert -size 500x250 xc:Red xc:Blue -append -spread 1000 -motion-blur 0x100+360 rojoazul-spread1000-motion-blur360.png
convert -size 500x250 xc:Red xc:Blue -append -spread 1000 -radial-blur 90 rojoazul-spread1000-radial-blur90.png
convert -size 500x250 xc:Red xc:Blue -append -spread 1000 -radial-blur 360 rojoazul-spread1000-radial-blur360.png
convert -size 500x250 xc:Red xc:Blue -append -radial-blur 360 -spread 1000 rojoazul-radial-blur360-spread1000.png
[code]convert -size 500x250 xc:Red xc:Blue -append -radial-blur 360 rojoazul-radial-blur360-sin-spread.png[/code]
En el anterior artículo contaba cómo pasar cadenas de texto a mayúsculas, a minúsculas o todas en minúsculas excepto la primera letra, que esa sí que pasaría, en caso de que no lo estuviera, a mayúsculas (por ejemplo para escribir nombres propios).
Aquí dejo otra pildorita sobre tr, cómo eliminar los espacios en blanco de una variable:
#!/bin/bash variable="Esto es una cadena de texto con espacios en blanco" sinespacios=$(echo "$variable" | tr -d '[[:space:]]') echo $sinespacios
Al ejecutar este script, la salida será:
Estoesunacadenadetextoconespaciosenblanco
Para transformar caracteres, bien para borrarlos o bien para modificarlos en GNU/Linux tenemos el comando tr, que cuenta con una serie de filtros para procesar en bloque cadenas de texto. En este artículo veremos dos de ellos: [:lower:] y [:upper:]
En la consola:
variable="LINUXCENTER"; echo $variable | tr '[:upper:]' '[:lower:]'
que devuelve:
linuxcenter
Todos los carecteres que recibe tr en mayúsculas ('[:upper:]') a mayúsculas ('[:lower:]')
En un script:
variable="LINUXCENTER" minusculas=$(echo $variable | tr '[:upper:]' '[:lower:]')
En consola:
variable="linuxcenter"; echo $variable | tr '[:lower:]' '[:upper:]'
Que devuelve:
LINUXCENTER
Ya que todos los caracteres que recibe tr en minúsculas ('[:lower:]') lo transforma en mayúsculas ([:upper:])
En un script:
variable="linuxcenter" mayusculas=$(echo $variable | tr '[:lower:]' '[:upper:]')
En consola:
variable="linuxcenter"; echo -n ${variable:0:1} | tr '[:lower:]' '[:upper:]'; echo ${variable:1} | tr '[:upper:]' '[:lower:]'
Que devuelve:
Linuxcenter
Veamos qué hace esta línea:
variable="linuxcenter"; -> Le damos un valor cualquiera a una variable.
echo -n ${variable:0:1} | tr '[:lower:]' '[:upper:]'; -> -n indica a echo que no envíe el salto de línea al final de la cadena que va a mostrar. ${variable:0:1} indica que extraiga desde la posición 0 un caracter y que lo pase a tr. Si está en minúscula, pasará el caracter a mayúscula.
echo ${variable:1} | tr '[:upper:]' '[:lower:]' -> ${variable:1} extrae la subcadena contenida en la variable $variable que va desde la posición 1 hasta el final.
Y en un script:
variable="linuxcenter" conmayuscula=$(echo -n ${variable:0:1} | tr '[:lower:]' '[:upper:]'; echo ${variable:1} | tr '[:upper:]' '[:lower:]')
En ImageMagick podemos seleccionar colores de varias maneras, principalmente mediante el nombre predefinido del color o mediante un código hexadecimal.
Ya hemos visto en otros artículos que para trabajar con los colores predefinidos por ImageMagick podemos listarlos con:
convert -list color
Y usar esos colores. Es una forma muy útil y práctica de trabajar, pero esto nos limita a unos cuantos colores, unos setecientos según la versión, podemos saber la cantidad con:
convert -list color | wc -l
Y además no nos permite trabajar con transparencias.
Por lo tanto, el trabajo con código hexadecimal, aunque nos obligue a un pequeño mayor esfuerzo a la hora de programar, nos ofrece una mayor cantidad de colores. Con los nombres de los colores predefinidos tenemos algo menos de 700 colores frente a los 255 * 255 * 255 colores con los que podemos trabajar si usamos códigos hexadecimales. Y si usamos el canal alpha para las transparencias, el número pasa a ser de 255 * 255 * 255 * 255 = 4.228,250.625 colores posibles.
Podemos escribir a mano el código:
convert -size 500x200 xc:#11223344 imagen.png
O podemos generar ese código aleatoriamente.
Para ello nos ayudaremos de shuf para generar un número aleatorio y bc para convertir de decimal a hexadecimal.
Y veamos una función que nos genere aleatoriamente un código hexadecimal de color en nuestros scripts. Pero que, además de dar la posibilidad de generar aleatoriamente un color aleatorio que le podamos indicar que tenga una tonalidad rojiza, verdosa o azulada, o bien que tenga menos coloración de uno de esos canales, que el color sea claro u oscuro y que tenga o no transparencia. Para ello uso una serie de parámetros en la función:
t T -t -T -> Se añade un canal Alfa de transparencia
d D -d -D o O -o -O -> oscuro o dark, el color generado será oscuro, entre 0 y 85 (255 / 3)
l L -l -L c C -c -C -> claro o light, el color será claro, entre 170 ( 255 / 3 * 2 ) y 255
gb GB -gb -GB bg BG -bg -BG -> Mayor cantidad de verde y azul que de rojo
rb RB -rb -RB br BR -br -BR -> Mayor cantidad de rojo y azul que de verde
rg RG -rg -RG gr GR -gr -GR -> Mayor cantidad de rojo y verde que de azul
b B -b -B -> Color azulado
g G -g -G -> Color verdoso
r R -r -R -> Color rojizo
Con esas premisas, aquí dejo la función en un script de ejemplo:
#!/bin/bash
function devuelvecolor()
{
if [ $1 = "r" ] || [ $1 = "R" ] || [ $1 = "-r" ] || [ $1 = "-R" ]
then
let decimalrojo=$(shuf -i 128-255 -n 1)
let decimalverde=$(shuf -i 0-127 -n 1)
let decimalazul=$(shuf -i 0-127 -n 1)
elif [ $1 = "g" ] || [ $1 = "G" ] || [ $1 = "-g" ] || [ $1 = "-G" ]
then
let decimalrojo=$(shuf -i 0-127 -n 1)
let decimalverde=$(shuf -i 128-255 -n 1)
let decimalazul=$(shuf -i 0-127 -n 1)
elif [ $1 = "b" ] || [ $1 = "B" ] || [ $1 = "-b" ] || [ $1 = "-B" ]
then
let decimalrojo=$(shuf -i 0-127 -n 1)
let decimalverde=$(shuf -i 0-127 -n 1)
let decimalazul=$(shuf -i 128-255 -n 1)
elif [ $1 = "rg" ] || [ $1 = "RG" ] || [ $1 = "-rg" ] || [ $1 = "-RG" ] || [ $1 = "gr" ] || [ $1 = "GR" ] || [ $1 = "-gr" ] || [ $1 = "-GR" ]
then
let decimalrojo=$(shuf -i 128-255 -n 1)
let decimalverde=$(shuf -i 128-255 -n 1)
let decimalazul=$(shuf -i 0-127 -n 1)
elif [ $1 = "rb" ] || [ $1 = "RB" ] || [ $1 = "-rb" ] || [ $1 = "-RB" ] || [ $1 = "br" ] || [ $1 = "BR" ] || [ $1 = "-br" ] || [ $1 = "-BR" ]
then
let decimalrojo=$(shuf -i 128-255 -n 1)
let decimalverde=$(shuf -i 0-127 -n 1)
let decimalazul=$(shuf -i 128-255 -n 1)
elif [ $1 = "gb" ] || [ $1 = "GB" ] || [ $1 = "-gb" ] || [ $1 = "-GB" ] || [ $1 = "bg" ] || [ $1 = "BG" ] || [ $1 = "-bg" ] || [ $1 = "-BG" ]
then
let decimalrojo=$(shuf -i 0-127 -n 1)
let decimalverde=$(shuf -i 128-255 -n 1)
let decimalazul=$(shuf -i 128-255 -n 1)
elif [ $1 = "l" ] || [ $1 = "L" ] || [ $1 = "-l" ] || [ $1 = "-L" ] || [ $1 = "c" ] || [ $1 = "C" ] || [ $1 = "-c" ] || [ $1 = "-C" ]
then
let decimalrojo=$(shuf -i 170-255 -n 1)
let decimalverde=$(shuf -i 170-255 -n 1)
let decimalazul=$(shuf -i 170-255 -n 1)
elif [ $1 = "d" ] || [ $1 = "D" ] || [ $1 = "-d" ] || [ $1 = "-D" ] || [ $1 = "o" ] || [ $1 = "O" ] || [ $1 = "-o" ] || [ $1 = "-O" ]
then
let decimalrojo=$(shuf -i 0-85 -n 1)
let decimalverde=$(shuf -i 0-85 -n 1)
let decimalazul=$(shuf -i 0-85 -n 1)
else
let decimalrojo=$(shuf -i 0-255 -n 1)
let decimalverde=$(shuf -i 0-100 -n 1)
let decimalazul=$(shuf -i 0-255 -n 1)
fi
if [ "$2" ]
then
if [ $1 = "t" ] || [ $1 = "T" ] || [ $2 = "t" ] || [ $2 = "T" ] || [ $1 = "-t" ] || [ $1 = "-T" ] || [ $2 = "-t" ] || [ $2 = "-T" ]
then
let decimaltransparencia=$(shuf -i 30-125 -n 1)
hexadecimaltransparencia=$(echo "ibase=10;obase=16;$decimaltransparencia" | bc)
if [ `expr length $hexadecimaltransparencia` -lt 2 ]
then
hexadecimaltransparencia="0"$hexadecimaltransparencia
fi
fi
fi
hexadecimalrojo=$(echo "ibase=10;obase=16;$decimalrojo" | bc)
if [ `expr length $hexadecimalrojo` -lt 2 ]
then
hexadecimalrojo="0"$hexadecimalrojo
fi
hexadecimalverde=$(echo "ibase=10;obase=16;$decimalverde" | bc)
if [ `expr length $hexadecimalverde` -lt 2 ]
then
hexadecimalverde="0"$hexadecimalverde
fi
hexadecimalazul=$(echo "ibase=10;obase=16;$decimalazul" | bc)
if [ `expr length $hexadecimalazul` -lt 2 ]
then
hexadecimalazul="0"$hexadecimalazul
fi
echo "#"$hexadecimalrojo$hexadecimalverde$hexadecimalazul$hexadecimaltransparencia
}
devuelvecolor R t
Como ya vimos en otro artículo, si queremos operar matemáticamente en Bash más allá de las operaciones básicas necesitamos ayudarnos de bc.
Una de las funcionalidades que nos ofrece bc es cambiar entre sistemas de numeración. Por ejemplo, para pasar de decimal a hexadecimal lo podemos hacer con:
decimal=100
hexadecimal=$(echo "ibase=10;obase=16;$decimal" | bc)
Sin embargo, para pasar de hexadecimal a decimal, no hace falta indicar la base de salida (obase), nos valdría con:
hexadecimal=AA
decimal=$(echo "ibase=16; $hexadecimal" | bc)
Aquí traigo un truco sencillo que uso mucho y que puede ser de gran interés para otras personas que automaticen muchos procesos y quieren que parezca que no está todo automatizado. Para simular que algo está hecho a mano, pero realmente está automatizado es necesaria siempre una dosis de aleatoriedad.
Y una forma interesante de aleatorizar los procesos automáticos es seleccionar de forma aleatoria las funciones que realizan tareas.
Para ello necesitamos tener las funciones almacenadas en un fichero distinto al script que las ejecuta e invocarla a través de:
. $bibliotecafunciones
Y seleccionar la función a usar con:
funcionausar=$(grep function $bibliotecafunciones | cut -d " " -f 2 | shuf -n1 | sed 's/()//')
Con esto lo que hacemos es meter en la variable funcionausar el resultado de grep function $bibliotecafunciones | cut -d " " -f 2 | shuf -n1 | sed 's/()//'.
Veamos qué hace cada parte de esta línea:
grep function $bibliotecafunciones -> Busca la cadena de texto function en el fichero cuyo nombre está almacenado en la variable $bibliotecafunciones
cut -d " " -f 2 -> Extrae la segunda columna (-f 2) de la cadena que ha recibido siendo los separadores entre columnas el espacio en blanco (-d " ")
shuf -n1 -> Ordena aleatoriamente lo que ha recibido y extrae la primera línea
sed 's/()//' -> Cambia la cadena de texto () por otra cadena vacía, ya que entre las barras no hay nada (//).
Y ahora vamos a ver un ejemplo que no hace nada, pero ilustra lo que estoy comentando:
#!/bin/bash
# Recibe en bibliotecafunciones dónde se encuentras las funciones
bibliotecafunciones="lib/bibliotecafunciones.sh"
# Incluye el código del fichero almacenado en $bibliotecafunciones en este script
. $bibliotecafunciones
# Iniciarlizar las variables que usaremos como parámetros para la función seleccionada
let parametro1=1
let parametro2=2
let parametro3=3
# Selecciona aleatoriamente la función a usar
funcionausar=$(grep function $bibliotecafunciones | cut -d " " -f 2 | shuf -n1 | sed 's/()//')
# Uso de la función
resultadodelafuncion=$(eval $funcionausar $parametro1 $parametro2 $parametro3)
Y ahora vamos a ver la biblioteca:
function funcionuno()
{
# Función que hace unas cosas
}
function funciondos()
{
# Función que hace otras cosas
}
function funcionuno()
{
# Función que hace cosas distinas
}
function funcionuno()
{
# Función que hace algo... o no
}
Hemos hecho un vídeo que no tiene sonido a partir de imágenes generadas con ImageMagick, o a partir de una única imagen, o hemos concatenado vídeos con ffmpeg o... sea como sea, resulta que tenemos un vídeo sin audio.
Y queremos que tenga audio para que sea más agradable su visionado.
Pero hay veces que nos ha salido un vídeo más largo, otras veces más pequeño... generalmente, al montar un vídeo no sabemos antes de empezar la duración exacta de dicho vídeo. Y si lo hemos hecho automáticamente con scripts, menos. Así que queremos vacilar de ser hackers de ImageMagick, deberemos de buscar, de forma automática, una canción que dure exactamente lo mismo que el vídeo.
Y aquí viene de nuevo vinfo.sh a ayudarnos. Y youtube-dl también puede ayudarnos mucho, ya que podemos buscar canciones con licencia creative commons en YouTube y descargarlas en un directorio. Y usarlas justo en el momento que coincida su duración con la duración del vídeo que acabamos de hacer.
¿Vemos cómo?
Aquí está el código, bastante autodescriptivo si has leído los anteriores artículos. Pero con una salvedad: si los nombres de las canciones, o de cualquier otro fichero puede que contenga espacios vacíos, deberemos cambiar el separador de campo.
#!/bin/bash # Script que recibe un vídeo como parámetro # calcula su duración y busca en un directorio # una canción que coincida en duración # y lo anexa. # Para mantener el nombre original el script # creará una copia temporal que luego borrará. # Para poder enlazar la canción y referenciar # al autor, acabará el script escribiendo # el nombre del fichero de la canción. directoriocanciones="instrumentales" # Cambia el separador de campo IFS=' ' # Extrae el nombre del fichero # y la extensión del fichero pasado como parámetro sinextension=${1%.*} extension=${1##*.} # Calcula la duración del audio del fichero pasado como argumento duracion=$(./vinfo.sh duration $1) duracionneta=${duracion:0:8} echo "La duracion del video es "$duracion # Crea el fichero temporal y borra el antiguo nuevofichero=$sinextension"-temp."$extension echo "El nuevo fichero será: "$nuevofichero # Busca una canción en el directorio de canciones # que tenga la misma duración que el vídeo for i in $(ls $directoriocanciones) do duracioncancion=$(./vinfo.sh duration "$directoriocanciones/$i") duracionnetacancion=${duracioncancion:0:8} echo "duracion video: "$duracionneta echo "duracion cancion: "$duracionnetacancion echo "cancion: "$i if [ "$duracionneta" = "$duracionnetacancion" ] then ffmpeg -i $1 -i "$directoriocanciones/$i" -c:v copy -c:a copy $nuevofichero rm $1 mv $nuevofichero $1 rm $nuevofichero echo $i break fi done
Cuando recorremos un directorio con ficheros cuyo nombre puede que contenga espacios en blanco (algo muy habitual si descargamos vídeos con youtube-dl), no funciona el típico:
#!/bin/bash
directorio="midirectorio"
for i in $(ls $directorio)
do
echo $i
done
Ni siquiera serviría entrecomillar el $i, ya que no le llegaría el nombre del fichero al recortarse en el for.
La solución es cambiar antes de hacer el for el separador de campo con:
IFS='
'
Quedando el script así:
#!/bin/bash
directorio="midirectorio"
IFS='
'
for i in $(ls $directorio)
do
echo $i
done
Guardad este artículo en favoritos. Es una de esas piedras en las que todos tropezamos una y otra vez. A menos yo. Pero ya tengo la chuleta y así ya no tendré que mirar una y otra vez scripts antiguos.
Para saber la duración de un vídeo en un script y usar esa información para tabajar con ella, podemos usar ffmpeg, avconf o un script de Gaspar Fernández disponible desde su web: https://poesiabinaria.net/2016/02/como-extraer-duracion-fotogramas-bitrate-y-fps-de-un-video-para-nuestros-scripts/
Y como en el script el autor indica claramente:
# Do whatever you want with this code.
Vamos a darle uso a esa libertad y usarlo para evolucionar su uso:
Almacenar la duración de un vídeo en una variable
Antes de nada, deberemos copiar al directorio de trabajo ese script y darle permisos de ejecución:
chmod + vinfo.sh
Con esto ya podremos invocarlo desde nuestros scripts y recoger la duración de los vídeos en variables:
duracion=$(./vinfo.sh duration $video)
Extraer algunos fotogramas por minuto de una serie de vídeos
Queremos hacer uno o más vídeos a partir de los brutos tomados en un evento, los típicos recursos que se utilizan mucho en las noticias, en los resúmenes de los eventos o para promocionar un evento a partir de los eventos anteriores. Y además, como nos encanta trabajar con ImageMagick queremos extraer fotogramas para luego meterle efectos y hacer más espectaculares esos clips.
Pues aquí tenemos una posible solución:
#!/bin/bash
# Script que recorre un directorio con brutos de vídeo
# Por cada minuto de duración del vídeo extrae de un punto aleatorio
# tres bloques de 200 fotogramas cada uno
# Guardará esos bloques en un nuevo directorio empaquetados en subdirectorios
# cuyo nombre será igual al nombre del bruto donde se ha extraído sin extensión
directoriooriginales="halloween"
directoriodestino="fotogramas-"$directoriooriginales
for i in $(ls $directoriooriginales)
do
# Quita la extensión del nombre del fichero original
# para que el directorio se llame igual
# y tener así una referencia en caso de tener que volver a usar el original
sinextension=${i%.*}
# Calcula la duración del vídeo
duracion=$(./vinfo.sh duration $directoriooriginales/$i)
horas=${duracion:0:2}
minutos=${duracion:3:2}
# Extrae los fotogramas y almacénalos en directorios numerados
# consecutivamente con el mismo nombre que el vídeo origina + numeral
let numeral=1
for min in $(seq 0 $minutos)
do
for tercios in $(seq 1 3)
do
let seg=$(shuf -i 0-59 -n 1)
mkdir $directoriodestino/$sinextension$numeral
ffmpeg -i $directoriooriginales"/"$i -ss 00:$min:$seg -vframes 200 $directoriodestino/$sinextension$numeral/captura%03d.png
let numeral++
done
done
done
Buscar una canción que se ajuste al tamaño de un vídeo para usarla como banda sonora
Recortar una canción para que se ajuste a la duración de un vídeo
Crear una pista de vídeo a partir de una cartela que se ajuste a la duración de un podcast