¿Hacemos una Webserie?

¿Hacemos una Webserie?

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.

 

 

Concatenar vídeos con ffmpeg no da siempre los resultados esperados, depende de la versión, de los codecs instalados y varios factores que pueden hacer que el script que en un equipo funciona perfectamente, en otro ordenador no funcione tan bien.

Lo que nunca falla es ImageMagick -sic-. Así pues, voy a comentar un script que funcionaría, o debería funcionar, en todos los sistemas:

El objetivo es el mismo que el del artículo sobre cómo componer vídeos con fotografías y sonido:

Un script que reciba un fichero de audio como parámetro, por ejemplo un podcast, aunque podría ser cualqueir otro fichero de audio, con una duración variable y que se genere un vídeo con unos elementos básicos para un vídeo de YouTube:

  • Entradilla
  • Una imagen principal que se intercale con una cartela con las redes sociales o cualquier otra imagen, por ejemplo, una llamada a la acción de suscribirse al canal o cualquier otra imagen.
  • Pantalla final

 La imagen de la llamada a la acción o las redes sociales podría ser también una animación o una secuencia de vídeo. Su adaptación simplemente sería cambiar la imagen fija por un vídeo y copiar la parte de la entradilla. De hecho, si he combinado vídeo con imagen fija ha sido precisamente para que este script pueda ser germen para otras adaptaciones.

Y, de paso, ver en acción el contenido explicado en otros artículos:


La explicación del código está en estos artículos. El resto, en los comentarios del script:

 

 #!/bin/bash

function numerodesegundos()
{
    
    let horasdeaudio=${1:0:2}
    let minutosdeaudio=${1:3:2}    
    let segundosdeaudio=${1:6:2}
    
    echo $[ ($horasdeaudio*60*60) + ($minutosdeaudio*60) + $segundosdeaudio ]
}

function compruebaentradilla()
{
# Comprueba si la entradilla tiene su directorio
# y están los fotogramas desmontados ahí.
#
# Si no lo está, crea el directorio y
# desmonta los fotogramas de la entradilla

if [ ! -d $directorioentradilla ]
then
    mkdir $directorioentradilla
    ffmpeg -i $entradilla $directorioentradilla/fotograma%04d.png
else
    let contenido=$(ls $directorioentradilla | wc -l)
    if [ $contenido -eq 0 ]
    then    
        ffmpeg -i $entradilla $directorioentradilla/fotograma%04d.png
    fi
fi

fotogramasentradilla=$(ls $directorioentradilla | wc -l)
echo $fotogramasentradilla
}

# Extrae el nombre del fichero de audio pasado como parámetro quitando la extensión
sinextension=${1%.*}

# Calcula la duración del audio del fichero pasado como argumento
duracion=$(./vinfo.sh duration $1)


# Ficheros auxiliares
entradilla="entradilla.mp4"
directorioentradilla="entradilla"
imagenprincipal="fondopodcast.png"
redessociales="redessociales.png"
pantallafinal="pantallafinal.png"


# Calcula la duración en segundos del audio y el número de fotogramas necesarios para el vídeo
segundosdeaudio=$(numerodesegundos $duracion)
let totalfotogramas=$segundosdeaudio*24

fotogramasentradilla=$(compruebaentradilla)

# Calcula la duración de la entradilla
duracionentradilla=$(./vinfo.sh duration $entradilla)
segundosentradilla=${duracionentradilla:6:2}

# Calcula cuánto duran los contenidos extras con dos cortes para la cartela de redes sociales

let duracionextras=$segundosentradilla+5+5+20

# Calcula la diferencia entre los segundos de audio del fichero parámetro y los extras

let tiempototalarellenar=$segundosdeaudio-$duracionextras

# Como vamos a hacer dos cortes para la cartela de redes sociales, divide el tiempo entre 3

let tercio=$tiempototalarellenar/3
let fotogramastercio=$tercio*24

##############################################################################
#
# Los bloques de imágenes quedarán así:
# Entradilla -> del fotograma 1 hasta que se acaben los fotogramas de la entradilla
# Imagen Principal -> desde último fotograma entradilla + 1 hasta un tercio de los fotogramas restantes más
# Cartela Redes Sociales: 5 segundos x 24 fotogramas = 120 fotogramas
#
# Nuevo bloque de: Imagen principal + cartela redes sociales + imagen principal
# Este bloque hay que repetirlo a mano, con un while que compruebe los bloques
# no funciona el convert imagen.png png:- para pasárselo a ffmpeg
#
# Pantalla final: 20 segundos x 24 fotogramas = 480 fotogramas
#
##############################################################################

contador=1
let fotogramasredessociales=120
let fotogramaspantallafinal=480

# Calcula los finales de los bloques:

let finprimerbloque=$fotogramasentradilla+1+$fotogramastercio
let finsegundobloque=$finprimerbloque+1+$fotogramastercio
let fintercerbloque=$finsegundobloque+1+$fotogramasredessociales
let fincuartobloque=$fintercerbloque+1+$fotogramastercio
let finquintobloque=$fincuartobloque+1+$fotogramasredessociales
let finsextobloque=$finquintobloque+1+$fotogramastercio
let finseptimobloque=$finsextobloque+1+$fotogramaspantallafinal

for i in $(seq 1 $totalfotogramas)
do
    if [ $contador -le $fotogramasentradilla ]
    then
        imagen=$(ls $directorioentradilla | head -n $contador | tail -n 1)
        let contador++
    elif [ $contador -gt $fotogramasentradilla ] && [ $contador -le $finprimerbloque ]
    then
        imagen=$imagenprincipal
        let contador++
    elif [ $contador -gt $finprimerbloque ] && [ $contador -le $finsegundobloque ]
    then
        imagen=$redessociales
        let contador++
    elif [ $contador -gt $finsegundobloque ] && [ $contador -le $fintercerbloque ]
    then
        imagen=$imagenprincipal
        let contador++
    elif [ $contador -gt $fintercerbloque ] && [ $contador -le $fincuartobloque ]
    then
        imagen=$redessociales
        let contador++
    elif [ $contador -gt $fincuartobloque ] && [ $contador -le $finquintobloque ]
    then
        imagen=$imagenprincipal
        let contador++
    elif [ $contador -gt $finquintobloque ] && [ $contador -le $finsextobloque ]
    then
        imagen=$imagenprincipal
        let contador++
    elif [ $contador -gt $finsextobloque ] && [ $contador -le $finseptimobloque ]
    then
        imagen=$pantallafinal
        let contador++
    fi

    convert $imagen png:-
done | ffmpeg -f image2pipe -i - pistadevideo.mp4

ffmpeg -i pistadevideo.mp4 -i $1 -c:v copy -c:a copy $sinextension.mp4
rm pistadevideo.mp4

 

 

Si queremos hacer un vídeo con una imagen fija y un audio, por ejemplo, para subir un podcast a YouTube, una opción sencilla es que lo haga ffmpeg directamente:

ffmpeg -loop 1 -y -i imagen.png -i podcast.mp3 -shortest video.mp4

Quizá nos de pereza escribirlo siempre, máxime si para hacer el mp3 del podcast hemos utilizado una cadena larga para el nombre con palabras clave para ser encontrados en el buscador de YouTube y no somos excesivamente aficionados al tabulador o a usar history, podemos hacer un script:

#!/bin/bash

sinextension=${1%.*}

ffmpeg -loop 1 -y -i fondopodcast.png -i $1 -shortest $sinextension.mp4

 

De esta manera, le pasamos como parámetro el fichero donde tengamos el podcast y crea con el mismo nombre y extensión .mp4 una pista de vídeo y la imagen que usemos habitualmente como fondo para los podcasts.

Pero esto nos permite únicamente trabajar con una imagen. Podríamos intentar con:

ffmpeg -loop 1 -y -i imagen.png -i imagen2.png -i podcast.mp3 -shortest video.mp4

Pero no incluye la segunda imagen. Otra opción es:

ffmpeg -i *.png -i podcast.mp3 video.mp4

Pero tampoco nos hace un vídeo interesante si queremos meter una imagen de 20 segundos como pantalla final para YouTube, alguna entradilla y alguna imagen de fondo variada, por lo que tenemos que elaborar un poco más el script.

Volvemos a usar el vinfo.sh de de Gaspar Fernández que podemos descargar desde su web: https://poesiabinaria.net/2016/02/como-extraer-duracion-fotogramas-bitrate-y-fps-de-un-video-para-nuestros-scripts/

Y con ese script y ffmpeg, hacemos un shell script que automatice la generación de una pista de vídeo con imágenes para poder subir un podcast a YouTube:

#!/bin/bash

function numerodesegundos()
{
    
    let horasdeaudio=${1:0:2}
    let minutosdeaudio=${1:3:2}    
    let segundosdeaudio=${1:6:2}
    
    echo $[ ($horasdeaudio*60*60) + ($minutosdeaudio*60) + $segundosdeaudio]
}

sinextension=${1%.*}
duracion=$(./vinfo.sh duration $1)

entradilla="entradilla.mp4"
imagenprincipal="fondopodcast.png"
redessociales="redessociales.png"
pantallafinal="pantallafinal.png"


# Clip con las redes sociales
ffmpeg -loop 1 -f image2 -i $redessociales -vcodec libx264 -t 5 redessociales.ts

# Salida con la pantalla final
ffmpeg -loop 1 -f image2 -i $pantallafinal -vcodec libx264 -t 20 salida.ts

# Calcula la duración de la entradilla
duracionentradilla=$(./vinfo.sh duration $entradilla)
segundosentradilla=${duracionentradilla:6:2}

# Convierte la entradilla en .ts
ffmpeg -i $entradilla entradilla.ts

# Calcula cuánto duran los contenidos extras con dos cortes para la cartela de redes sociales

let duracionextras=$segundosentradilla+5+5+20

# Calcula cuántos segundos dura el audio pasado como parámetro

segundosdeaudio=$(numerodesegundos $duracion)

# Calcula la diferencia entre los segundos de audio del fichero parámetro y los extras

let tiempototalarellenar=$segundosdeaudio-$duracionextras

# Como vamos a hacer dos cortes para la cartela de redes sociales, divide el tiempo entre 3

let tercio=$tiempototalarellenar/3

# Y crea la pieza de vídeo con la imagen principal del podcast con esa duración:

ffmpeg -loop 1 -f image2 -i $imagenprincipal -vcodec libx264 -t 5 imagenprincipal.ts

# Monta una pista de vídeo con las piezas generadas hasta ahora:

ffmpeg -i "concat:entradilla.ts|imagenprincipal.ts|redessociales.ts|imagenprincipal.ts|redessociales.ts|imagenprincipal.ts|salida.ts" -c copy pistadevideo.ts

# Conviértelo en mp4

ffmpeg -i pistadevideo.ts -acodec copy -vcodec copy pistadevideo.mp4

# Monta el vídeo final con la pista de video generada con las imágenes y el podcast

ffmpeg -i pistadevideo.mp4 -i $1 -c:v copy -c:a copy $sinextension.mp4

# Borra todos los ficheros auxiliares generados

rm redessociales.ts
rm entradilla.ts
rm salida.ts
rm imagenprincipal.ts
rm pistadevideo.ts

 

Pueden comprobar cómo quedan los podcast con estas cartelas en mi canal. Y si les ha servido este tutorial, les gusta el mundo audiovisual y el software libre, o simplemente, si les apete, pueden suscribirse:

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

 

Otros usos de este script:

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

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.

Extraer únicamente los x primeros o últimos caracteres

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.

Hacer que tail y head muestren el nombre del fichero

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?

Cómo 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 ;)

 

Más usos de tail y head

Mostrar el primer y último fichero de un directorio

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)

Extraer colores consecutivos para hacer un degradado

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:

celeste.png

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.

Extraer únicamente los x primeros o últimos caracteres

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.

Hacer que tail y head muestren el nombre del fichero

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?

Cómo 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 ;)

 

Más usos de tail y head

Mostrar el primer y último fichero de un directorio

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)

Extraer colores consecutivos para hacer un degradado

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:

celeste.png

Porque head y tail no sólo sirven para trabajar con ficheros de texto. También ayudan a hacer degradados ;)

 

Ya hemos visto que operar con decimales en Bash no es imposible, aunque es farragoso. Ahora veremos que pasa lo mismo para comparar decimales. Pero lo explico y dejo una función para que quien tenga que comparar, la copie en su script.

Primero comprobaremos si le hemos pasado bien los parámetros $1 y $2 a la función. Comprobamos si existen y tienen contenido. Si esto ocurre, comprobaremos que realmente son números que opcionalmente tienen un punto.

En caso de que sólo hubiera una variable numérica, se devuelve esa, ya que esa será la mayor.

Si las variables no fueran numéricas, se devuelve -1 de error.

Si ambas variables son nuḿéricas, se dividen en la parte entera y la parte decimal. Es decir, se extrae la subcadena desde el inicio hasta el punto, que es la parte entera, y la subcadena desde el punto hasta el fina, que es la parte decimal.

Comparamos las partes enteras. Si las partes enteras son distintas, el número mayor es el que tenga la parte entera mayor.

En caso de que las partes enteras sean iguales, seguimos haciendo comprobaciones:

Como recortamos subcadenas, igualamos su longitud con ceros al final de la subcadena más corta. Para que 0.02 no de mayor que 0.1.

Una vez que sabemos que la función ha recibido dos variables, que estas son numéricas con o sin punto, que hayamos separado la parte entera y la decimal, que las partes enteras son iguales y que las partes decimales tienen los mismos caracteres, comparamos los decimales.

Y devolvemos el parámetro cuya parte decimal sea mayor.

En caso de que la parte entera sea igual y la parte decimal sea igual tamibén, es que los dos números son iguales. Así que nos da igual cuál devolver. Devolveremos $1.

Y aquí el script con comentarios porque la función es un poco liosa:

<

[code]#!/bin/bash

function cualesmayor()
{
# Función que recibe dos parámetros con números decimales o no
# y devuelve el mayor de los dos

# Comprobamos que recibimos los dos parámetros,
# si recibimos solo uno, devolvemos ese

if [ "$1" ] && [ "$2" ]
then
# Existen ambas variables, por lo que podemos compararlas
# Comprobamos si ambos parámetros están formados únicamente
# por números y un único .

caracteresvalidos='^[0-9]+([.][0-9]+)?$'

if [[ $1 =~ $caracteresvalidos ]] && [[ $2 =~ $caracteresvalidos ]]
then
# Ambos son numéricos. Por lo que separaremos
# en la parte entera y la parte decimal

# Dónde está el punto. Si no hubiera punto, la posición es 0
let posicionpunto1=$(expr index $1 ".")
let posicionpunto2=$(expr index $2 ".")

# Extraemos las partes enteras.
if [ $posicionpunto1 -lt 0 ]
then
let parteentera1=$1
else
let numerodigitosparteentera1=$posicionpunto1-1
let parteentera1=${1:0:$numerodigitosparteentera1}
partedecimal1=${1:$posicionpunto1}

let numerodigitosparteentera2=$posicionpunto2-1
let parteentera2=${2:0:$numerodigitosparteentera2}
partedecimal2=${2:$posicionpunto2}

# Comprobamos las partes enteras

if [ $parteentera1 -gt $parteentera2 ]
then
echo $1
elif [ $parteentera1 -lt $parteentera2 ]
then
echo $2
else

# Ambas partes enteras son iguales
# Comprobamos las partes decimales
# Primero igualamos con ceros al final
# la longitud de las cadenas

tamdecimales1=${#partedecimal1}
tamdecimales2=${#partedecimal2}

if [ $tamdecimales1 -gt $tamdecimales2 ]
then
let diferencia=$tamdecimales1-$tamdecimales2
for i in $(seq 1 $diferencia)
do
partedecimal2=$partedecimal2"0"
done

elif [ $tamdecimales1 -lt $tamdecimales2 ]
then
let diferencia=$tamdecimales2-$tamdecimales1
for i in $(seq 1 $diferencia)
do
partedecimal1=$partedecimal1"0"
done
fi

if [ $partedecimal1 -gt $partedecimal2 ]
then
echo $1
elif [ $partedecimal1 -lt $partedecimal2 ]
then
echo $partedecimal2
else
# Ambas partes enteras son iguales
# Ambas partes decimales son iguales
# Ambos números son iguales
# Devolvemos el primero
echo $1
fi
fi

fi
else
echo -1
fi

else
if [ "$1" ] && [ ! "$2" ]
then
# $1 existe y no está vacío y $2 no existe o está vacío.
# Lo contrario nunca ocurrirá, ya que sin $1 no puede haber $2
echo $1
else
# Si llegamos aquí es porque no existe $1
echo -1
fi

fi
}

let operador1=3
let operador2=2

let operador3=5
let operador4=1

division1=$(echo "scale=4; $operador1/$operador2" | bc)
division2=$(echo "scale=4; $operador3/$operador4" | bc)

mayor=$(cualesmayor $division1 $division2)

echo "El primer resultado es "$division1" y el segundo resultado, "$division2". El mayor es "$mayor[code]

Para saber si una variable existe y está inicializada con un valor distinto a vacío, es decir, que no se ha inicializado con:

variable=""

Lo podemos comprobar con [ "$variable" ]

Veamos un ejemplo y vamos a ir evolucionándolo para ver sus posibilidades:

#!/bin/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

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.

 

 

 

¡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í