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.
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/bash
colores=""
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" "
done
convert -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/bash
colores=""
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" "
done
convert -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 ;)