Mostrando las entradas con la etiqueta scripts BASH. Mostrar todas las entradas
Mostrando las entradas con la etiqueta scripts BASH. Mostrar todas las entradas

Dominando la creación de archivos con "Here-Documents" (here-docs) en BASH


Dominando la creación de archivos con "Here-Docs" en BASH.

En el desarrollo de scripts, a menudo necesitaremos crear archivos de configuración XML, JSON, o bien scripts auxiliares que contendrán múltiples líneas de texto. Lo básico es escribir un "echo" tras otro, completando toda la secuencia.
Pero existe un método que creo que es mucho más elegante y bonito, el cual consiste en utilizar un "Here-Document" (o "Here-Doc").

Automatizando la configuración: el poder de "sed".

Imaginemos que necesitamos insertar una configuración específica en el archivo de acciones de Thunar (uca.xml), y que en lugar de escribir el archivo manualmente, utilizamos este bloque de código basado en el comando sed:

sed -i "/<\/actions>/i\
<action>\\
    <icon>terminal</icon>\\
    <name>ThunExec</name>\\
    <command>$terminal_predef</command>\\
    <description>Ejecuta en terminal preferida</description>\\
    <patterns>*.sh;*.bash;*.zsh;*.fish</patterns>\\
    <appears-on-files>true</appears-on-files>\\
</action>" "$thun_uca"


¿Qué hace este bloque?

Es un "inyector" de código. Busca la etiqueta de cierre </actions> dentro del archivo, y justo antes de ella (i), inserta todo el bloque <action>...</action> que configuramos. Es una forma quirúrgica de añadir funciones a un archivo de configuraciones, sin borrar lo que allí ya existía.

  • La primera "i" (sed -i...) es una opción del comando sed que significa "in-place", la cual le dice al comando: "no me muestres el resultado por pantalla, editá directamente el archivo original". Si no pusieses esta "i", sed solo imprimiría los cambios en la terminal y el archivo no se modificaría.

  • La segunda i (la del medio), es el comando de inserción "insert". En la sintaxis de sed, cuando ponés una barra "/busqueda/i", le estás diciendo al comando: "en la línea en donde encuentres esta búsqueda, insertá lo que siga a continuación, pero justo antes de esa línea".


El desafío: ¿cómo crear el archivo inicial?

Para que el comando "sed" anterior funcione, el archivo "$thun_uca" debe existir previamente y contener al menos las etiquetas básicas. Para lograr esto, tenemos dos caminos.

1. La "millonada de echos vomitantes". 

Es la opción que utilizan muchos programadores. Es funcional, pero un poco más difícil de mantener, además de ser menos elegante:

if [[ ! -f "$thun_uca" ]];then
    echo '<?xml version="1.0" encoding="UTF-8"?>' > "$thun_uca"
    echo '<actions>' >> "$thun_uca"
    echo '</actions>' >> "$thun_uca"
fi


Aquí solo hay 3 "echos", pero... ¿podés imaginar 30 o 40 de ellos?
El problema aquí es que cada línea nueva requiere un comando echo y una redirección, lo que convierte un bloque simple en una sucesión de instrucciones que  redundan sobre la misma estructura que se repite una y otra vez. Si usáramos el método tradicional, el script se volvería una tarea potencialmente problemática, ya que habría que copiar/pegar líneas "como quien lava y no escurre" o estar repitiendo código de manera enfermiza, como la máquina que NO somos.

2. La forma más elegante: los "Here-Doc"

Esta es la técnica de la que habla este artículo. Es la forma más limpia, porque nos permite definir el "molde" del archivo de una sola vez, manteniendo la estructura XML intacta y legible. Un "Here-Doc" es una forma de decirle a BASH: "tomá todo lo que se escriba a continuación como una única unidad de texto y envialo todo junto al destino". Algo así como el Hypertransport de AMD (enviar más, de un solo golpe) frente al Hyper-Threading de Intel (enviar menos, de a "golpecitos").

if [[ ! -f "$thun_uca" ]];then
cat > "$thun_uca" << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<actions>
</actions>
EOF
fi


Aquí, el bloque <actions></actions> actúa como el contenedor necesario. Una vez que este bloque crea el archivo base, el comando sed que vimos antes entrará en juego para insertar la acción "ThunExec" justo en medio de esas dos etiquetas.

¿Cómo interpretar este código?

cat > "$thun_uca": El comando cat toma lo que recibe y lo escribe en el archivo indicado por la variable $thun_uca. El símbolo > asegura que, si el archivo existe, se sobrescriba.

<< 'EOF': acá ocurre la magia. Las llaves << inician el Here-Doc. La palabra EOF es simplemente un marcador que elegimos nosotros (podría ser cualquier palabra, por ejemplo "POCHO", pero EOF —End Of File— es el estándar).

Importante: al envolver EOF en comillas simples ('EOF'), le estamos dando una instrucción vital al shell: "no interpretes nada de lo que hay adentro, sé literal". Esto evita que BASH busque variables con $ para expandirlas o intente ejecutar comandos dentro del texto, en lugar de tratar a todo como texto plano.

El final del bloque: el EOF que aparece al final, solo y sin sangrías, le avisa a BASH: "hasta acá llegó el archivo".

Reglas de oro para estudiantes.

El delimitador final debe estar solo: nunca pongas espacios o tabulaciones antes del EOF de cierre, porque si lo hacés, BASH no reconocerá el fin del bloque y el script se quedará "colgado" esperando el cierre.

Literalidad: gracias a las comillas simples ('EOF'), podés copiar y pegar cualquier fragmento de código (HTML, XML, JSON, Python) dentro del bloque sin miedo a que los caracteres especiales (como $ o ") rompan el flujo de tu script.

Orden: esta técnica es la forma más "limpia" de gestionar configuraciones. Mantiene el contenido del archivo de destino visualmente idéntico a cómo debería quedar en el medio de almacenamiento, facilitando mucho la edición futura.

Nota importante sobre la sangría (indentación, tabulación) dentro del "Here-Doc".

Indentar, tabular, sangrar el texto al programar es "mandatory!", al igual que comentar lo no obvio.
Es posible que al escribir tu código dentro del Here-Doc quieras usar tabulaciones o espacios para que el XML se vea ordenado. Podés hacerlo sin problemas, pero con una advertencia: el delimitador de cierre (el EOF, el POCHO) debe estar pegado contra el margen izquierdo de la pantalla. Si ponés espacios antes del EOF final, BASH pensará que esos espacios son parte del contenido que debe buscar, no los encontrará, y tu script se quedará "colgado" esperando el cierre.

El truco avanzado: si necesitás que el código dentro del bloque también tenga sangría visual y que el EOF final pueda estar alineado con ella, podés usar <<-EOF (agregando un guión después de los signos "menor que"). Ese mismo guión será el que le indique a BASH que ignore todas las tabulaciones iniciales de cada línea.

Así es cómo quedaría el código, aplicando el truco del guión:

if [[ ! -f "$thun_uca" ]];then
    cat > "$thun_uca" <<- 'EOF'
        <?xml version="1.0" encoding="UTF-8"?>
        <actions>
        </actions>
    EOF
fi


Como podés ver, tanto el bloque interior como el EOF de cierre pueden estar perfectamente indentados, y BASH, gracias a ese guión, ignorará esas tabulaciones iniciales al procesar el archivo. Esto hace que el código quede en consonancia con los "case", "if", etc, que ya poseas en tu script.

NOTA EXTREMADAMENTE IMPORTANTE: para que esto funcione, la sangría dentro del bloque debe hacerse con tabulaciones reales ([Tab]) y no con espacios ([Space]), ya que <<- está diseñado específicamente para ignorar caracteres de tabulación, no "espacios puestos con la barra espaciadora", al inicio de cada línea incluida en el bloque.

De la estructura clásica a la potencia del "cortocircuito" en BASH


De la estructura clásica a la potencia del "cortocircuito" en BASH.


En BASH, existen muchas formas de tomar decisiones.
En este artículo, vamos a hacer un breve "viaje" desde la estructura más básica del "if" hasta una técnica avanzada y compacta que verás en los scripts que más experiencia acumulada encierran: el operador de "cortocircuito".

1. Punto de partida: el "if" básico.


Todo estudiante comienza por aquí. Es la forma más legible y segura de ejecutar algo si se necesita verificar el cumplimiento de una condición.

# Si el archivo existe, notificarlo.
if [[ -f "$archivo" ]];then
    echo "El archivo existe."
fi


2. Agregando alternativas: "else" y "elif".

En cuanto el script comienza a crecer, es necesario empezar a manejar el "qué pasa si no", y aquí es en donde entran en escena los actores "else" (si no) y "elif" (y si).

if [[ -f "$archivo" ]];then
    echo "Es un archivo regular."
elif [[ -d "$archivo" ]];then
    echo "Es un directorio."
else
    echo "No se encontró nada con ese nombre."
fi


3. El salto al minimalismo: operadores && y { }

A veces, podemos juzgar que un bloque "if" de 5 líneas para una tarea muy pequeña está ocupando "demasiado espacio visual", y es este tipo de situación el origen del código compacto, utilizando, entre otras herramientas, al operador "&&" (AND).

En BASH, el "&&" le dice al sistema: "ejecutá lo que sigue SOLO SI la operación anterior terminó con éxito".

Si solo querés ejecutar un comando, la cuestión es simple:

[[ -f "$archivo" ]] && echo "Existe."


¿Te vas dando cuenta de cómo funciona esto?

Ahora... ¿qué pasa si querés ejecutar varios comandos en una sola línea?
Para esto mismo es que están las llaves "{}", las cuales se pueden utilizar en combinación con "&&" (AND - "si esto es cierto, hacé lo siguiente") y "||" (OR - "si lo anterior falló, hacé esto").

[[ -f "$archivo" ]] && { echo "Es un archivo regular."; exit; }
[[ -d "$archivo" ]] && { echo "Es un directorio."; exit; }
echo "No se encontró nada con ese nombre."


Este código es equivalente al "if" completo (if/else/elif/fi) de más arriba
Son 3 líneas en comparación con 7: menos de la mitad,

Y podemos llevarlo al extremo "oneliner", que es una técnica que yo usaba mucho cuando programaba en BASIC, hace muchos años atrás:

[[ -f "$archivo" ]] && { echo "Es un archivo regular."; exit; };[[ -d "$archivo" ]] && { echo "Es un directorio."; exit; };echo "No se encontró nada con ese nombre."

Una sola línea. ¿Qué me contás?

Reglas de oro para usar las llaves.

  • Espacios obligatorios: siempre debe haber un espacio después de { y antes de }
  • El punto y coma final: el último comando dentro de las llaves debe terminar en punto y coma antes de cerrar la llave, para que BASH sepa que el bloque terminó. Esto no es como lo grotesco de Java, al que hay que estar avisándole en cada línea que la línea se terminó, poniendo SIEMPRE ";". Esto en BASH significa "se terminó la línea", pero NO ES OBLIGATORIO, y de hecho, se usa extremadamente poco.


¿Cuándo utilizar cada estilo?

Debes utilizar el "if/elif/else/fi" tradicional cuando se trate de una lógica compleja o estés escribiendo un script que otros (o vos mismo en el futuro) necesiten leer con total claridad y sin andar interpretando "código barroco".

Te recomiendo que, si vas a utilizar este método de "&&", "||" y llaves "{ ...; }", lo hagas ante una validación rápida y corta. Ahorra mucho espacio y, una vez que te acostumbrás a leerlo, hace que tu código se vea mucho más dinámico y fluido.

Simplificando tu código BASH: de bloques "if" a soluciones minimalistas.


Simplificando tu código BASH: de bloques "if" a soluciones minimalistas.


Cuando estamos aprendiendo a programar en "shell", tendemos a usar estructuras largas para tareas simples. 
Vamos a ver cómo transformar un bloque de código común en una solución que si hablamos de programas de código muy extenso, puede llegar a ser mucho más profesional, y también de cómo gestionar archivos de configuración de forma más "limpia".

El desafío de las variables vacías.

Imaginemos que nuestro script necesita saber el nombre del usuario. Si por alguna razón no logra saberlo, queremos que guarde un mensaje "por omisión". Normalmente, escribiríamos algo así, lo cual estaría muy bien:

if [[ -z "$usuario" ]];then
    usuario="Desconocido"
fi


Esto funcionará, pero BASH tiene un operador de expansión conocido como "parámetro por defecto" que hace exactamente lo mismo en una sola línea, llenando él mismo el contenido de la variable sin tener que dar tanta vuelta nosotros a nivel código, y sin pedirle a él que de tanta vuelta interna:

usuario="${usuario:-Desconocido}"


¿Cómo se interpreta esto?

Las llaves "${ }" le dicen a BASH que preste atención al estado de la variable.
El símbolo ":-" actúa como una especie de "plan B" en caso de que la variable "usuario", por algún motivo, esté vacía o no haya sido inicializada, y entonces poder utilizar el texto que sigue.
Si ya tiene un valor, no toca nada. Es ideal para configurar valores iniciales de manera automatizada, sin tener que "llenar" el script de condiciones o igualaciones.
Va otro ejemplo, para terminar de redondear el tema.

Supongamos que tu script permite elegir un color de fondo, pero que si el usuario no elige ninguno, el script tenga que utilizar uno obligatoriamente.

La forma más típica:

if [[ -z "$col_fondo" ]];then
    col_fondo="azul"
fi


La forma que estamos estudiando en este artículo:

col_fondo="${col_fondo:-azul}"

En este caso, el "plan B" asegura que el script siempre tenga un color para referenciarse y no fallar, sin importar si el usuario lo omitió deliberadamente o se olvidó de configurarlo con anterioridad a la ejecución de dicha línea.

Arquitectura del movimiento y la persistencia visual: el Pacman y la máscara binaria.


Arquitectura del movimiento y la persistencia visual: el Pacman y la máscara binaria.

Análisis del script de presentación ASCII de los videos de Entropía binaria para 2026.

En la programación de animaciones en terminal, mover un objeto de un punto A a un punto B es trivial; lo verdaderamente complejo surge cuando ese objeto debe interactuar con el entorno, modificándolo (permanentemente) a su paso. En el caso del script que estamos estudiando, el desafío es doble: el personaje debe recorrer la pantalla en un patrón de zigzag preciso y, al mismo tiempo, "ir comiéndose" al planeta para revelar un logotipo oculto debajo. Para lograr esto, no podemos pensar en el movimiento y en el dibujo como procesos separados; ambos dependen de un mapa de memoria compartido.

Para poder comprender el movimiento, primero debemos observar cómo se gestionan los límites. El Pacman no se mueve libremente; está confinado a un bucle lógico. En cada ciclo del procesador, su coordenada horizontal varía según una variable de dirección. Sin embargo, para lograr el efecto de escritura en zigzag, el script evalúa constantemente si el personaje ha superado los márgenes predefinidos a la izquierda o a la derecha del planeta. Cuando esto ocurre, suceden tres cosas simultáneamente: la dirección se invierte (se multiplica por -1), la coordenada vertical disminuye para subir un renglón, y el personaje se reposiciona en el borde opuesto. Este comportamiento cíclico asegura que el personaje barra toda la superficie del objeto central sin necesidad de programar cada giro manualmente.

Pero la magia real del script reside en cómo "borra" el planeta sin borrar el logo. Aquí es en donde entra en juego el concepto de matriz asociativa "array asociativo". En BASH, una matriz (array) común, se indiza (indexa) con números (0, 1, 2,...), pero un array asociativo nos permite usar cadenas de texto como índices. El script declara una variable llamada visitados. Cada vez que el Pacman se mueve, no solo cambia su posición en pantalla, sino que calcula qué coordenadas del planeta está tocando en ese momento. Luego, registra esas coordenadas exactas en el array asociativo usando el formato "fila,columna" como clave y asignándole un valor de "1". Es como si el Pacman fuera dejando un rastro de migajas invisibles en la memoria del ordenador, marcando "aquí estuve".

El paso final ocurre en el motor de renderizado. Cuando el script se dispone a dibujar el planeta línea por línea, no imprime ciegamente los caracteres del planeta. En su lugar, para cada celda de la cuadrícula, consulta el array visitados (de ahí su nombre). El programa se hace una pregunta simple: "¿ha pasado el Pacman por la coordenada Y,X actual?". Si la respuesta es no (el valor no existe o es 0), el script dibuja el caracter correspondiente al planeta (tierra o agua). Pero si la respuesta es sí (el valor es 1), el script ignora el planeta y busca en su lugar qué caracter corresponde a esa misma posición en el sprite del logotipo de Entropía binaria. De esta forma, no estamos realmente "borrando" nada; estamos alternando dinámicamente entre dos capas de imágenes (la capa del planeta y la capa del logo) utilizando la posición histórica del Pacman como una máscara de recorte en tiempo real.

Disección del código: implementación del motor de movimiento y revelado.

A continuación, he extraído y aislado la lógica exacta que controla el movimiento en zigzag, la detección de colisión (máscara) y el renderizado condicional.

Nota: para que este código sea funcional de manera independiente sin necesidad de descargar los archivos de texto externos (arte_ascii/...), he sustituido la función de carga de archivos por la definición manual de dos sprites simples (un cuadrado de "Planeta" y un cuadrado de "Logo") y un Pacman básico. El resto de la lógica (movimiento, array visitados, actualizar_mascara y el bucle de dibujo) es textualmente la del script original.


#!/bin/bash

c_borde_planeta="\e[34m"
c_tierra_planeta="\e[33m"
c_agua_planeta="\e[36m"
c_pacman="\e[1;33m"
c_texto="\e[1;37m"

# Colores logo.
c_logo_1_relleno="\e[38;5;52m"  # Marrón oscuro.
c_logo_1_fondo="\e[38;5;220m"   # Dorado.

# Ajustes de posición planeta y logo.
ajuste_planeta_y=0; ajuste_planeta_x=0
ajuste_logo_y=0; ajuste_logo_x=0

limite_pacman_izq=19
limite_pacman_der=5

vel_movimiento=50000000         # Velocidad pacman comiendo.
vel_anim=150000000              # Animación boca (150ms).

dir_arte="arte_ascii"

if [[ ! -d "$dir_arte" ]];then echo "Error: Falta '$dir_arte'";exit 1;fi

tput civis;tput rmam;clear      # rmam = desactivar auto-margen.
trap "tput cnorm;tput smam;echo -e '\e[0m';clear;exit" SIGINT 
c_reset="\e[0m"

# Estado inicial.
estado_juego=0        
filas_visitadas=0     
set_color_logo=1      
texto_pac=""          

declare -A visitados

# CARGA DE SPRITES DESDE ARCHIVOS DE TEXTO PLANO.
cargar_sprite() {
    local archivo="$dir_arte/$1"
    local -n ref_arr=$2; local -n ref_ancho=$3; local -n ref_alto=$4
    
    if [[ ! -f "$archivo" ]];then echo "Error: falta '$archivo'";tput cnorm;exit 1;fi
    
    mapfile -t ref_arr < "$archivo"
    ref_alto=${#ref_arr[@]}
    ref_ancho=0
    
    for linea in "${ref_arr[@]}";do
        len=${#linea}
        if [[ $len -gt $ref_ancho ]];then ref_ancho=$len;fi
    done
}

declare -a arte_planeta arte_logo pac_cerrado pac_abierto_izq pac_abierto_der 
cargar_sprite "planeta"             arte_planeta     ancho_planeta   alto_planeta
cargar_sprite "logo_entropía"       arte_logo        ancho_logo      alto_logo
cargar_sprite "pacman_cerrado"      pac_cerrado      ancho_pac       alto_pac
cargar_sprite "pacman_abierto_izq"  pac_abierto_izq  tmp_w           tmp_h
cargar_sprite "pacman_abierto_der"  pac_abierto_der  tmp_w           tmp_h

# POSICIONAMIENTO INICIAL.
ancho_term=$(tput cols);alto_term=$(tput lines)

offset_x=$(( ((ancho_term - ancho_planeta) / 2) + ajuste_planeta_x ))
offset_y=$(( ((alto_term - alto_planeta) / 2) + ajuste_planeta_y ))

logo_rel_x=$(( ((ancho_planeta - ancho_logo) / 2) + ajuste_logo_x ))
logo_rel_y=$(( ((alto_planeta - alto_logo) / 2) + ajuste_logo_y ))

pac_x=$(( offset_x + ancho_planeta + 2 ))
pac_y=$(( offset_y + alto_planeta - alto_pac )) 
pac_dir=-1;estado_pac=0;idx_texto=0

# LÓGICA DE MÁSCARA (EL BORRADO)
actualizar_mascara() {
    local -n ref_sprite_actual
    if [[ $estado_pac -eq 0 ]];then ref_sprite_actual=pac_cerrado
    elif [[ $pac_dir -eq 1 ]];then ref_sprite_actual=pac_abierto_der
    else ref_sprite_actual=pac_abierto_izq
    fi

    local rel_pac_x=$((pac_x - offset_x))
    local rel_pac_y=$((pac_y - offset_y))

    for ((py=0; py<alto_pac; py++));do
        local linea_sprite="${ref_sprite_actual[py]}"
        for ((px=0; px<ancho_pac; px++));do
            local char_en_sprite="${linea_sprite:px:1}"
            if [[ "$char_en_sprite" != " " ]] && [[ -n "$char_en_sprite" ]];then
                local ly=$((rel_pac_y + py))
                local lx=$((rel_pac_x + px))
                
                if [[ $ly -ge 0 ]] && [[ $ly -lt $alto_planeta ]] && [[ $lx -ge 0 ]] && [[ $lx -lt $ancho_planeta ]];then
                    visitados["$ly,$lx"]=1
                fi
            fi
        done
    done
}

# MOTOR GRÁFICO (RENDERIZADO).
dibujar_escena() {
    buffer="\e[H\e[2J" 

    # PLANETA / LOGO Eb.
    local c_relleno=$c_logo_1_relleno
    local c_fondo=$c_logo_1_fondo

    for ((i=0;i<alto_planeta;i++));do
        local pantalla_y=$((offset_y + i))
        buffer+="\e[${pantalla_y};${offset_x}H"
        
        local linea_planeta="${arte_planeta[i]}"
        local idx_fila_logo=$((i - logo_rel_y))
        local linea_logo=""
        if [[ $idx_fila_logo -ge 0 ]] && [[ $idx_fila_logo -lt $alto_logo ]];then linea_logo="${arte_logo[$idx_fila_logo]}";fi

        local linea_construida=""
        for ((j=0; j<ancho_planeta; j++)); do
            # LÓGICA DE VISIBILIDAD.
            local mostrar_logo=0
            if [[ "${visitados["$i,$j"]}" == "1" ]];then mostrar_logo=1;fi
            
            if [[ $mostrar_logo -eq 1 ]];then
                # MODO "LOGO".
                local idx_col_logo=$((j - logo_rel_x))
                if [[ $idx_fila_logo -ge 0 ]] && [[ $idx_fila_logo -lt $alto_logo ]] && [[ $idx_col_logo -ge 0 ]] && [[ $idx_col_logo -lt $ancho_logo ]];then
                    local char="${linea_logo:idx_col_logo:1}"
                    
                    if [[ "$char" == "#" ]];then linea_construida+="${c_fondo}█${c_reset}"
                    elif [[ "$char" == " " ]] || [[ -z "$char" ]];then linea_construida+=" "
                    else linea_construida+="${c_relleno}█${c_reset}"
                    fi
                    
                else
                    linea_construida+=" "
                fi
            else
                # MODO "PLANETA".
                local char="${linea_planeta:j:1}"
                if [[ -z "$char" ]]; then char=" ";fi
                
                if [[ "$char" == "█" || "$char" == "#" ]];then linea_construida+="${c_tierra_planeta}${char}${c_reset}"
                elif [[ "$char" == "-" || "$char" == "=" || "$char" == ":" || "$char" == "." ]];then linea_construida+="${c_borde_planeta}${char}${c_reset}"
                else linea_construida+="${c_agua_planeta}${char}${c_reset}";fi
            fi
        done
        buffer+="$linea_construida"
    done

    # PACMAN.
    local -n ref_p
    if [[ $estado_pac -eq 0 ]];then ref_p=pac_cerrado
    elif [[ $pac_dir -eq 1 ]];then ref_p=pac_abierto_der
    else ref_p=pac_abierto_izq;fi

    local j=0
    
    for pline in "${ref_p[@]}";do
        local py=$((pac_y + j))
        local linea_transparente="${pline// /\\e[1C}"
    
        if [[ $py -gt 0 ]] && [[ $py -lt $alto_term ]];then
            buffer+="\e[${py};${pac_x}H${c_pacman}${linea_transparente}${c_reset}"
        fi
        
        ((j++))
    done
    
    echo -ne "$buffer"
}

# BUCLE PRINCIPAL.
ultimo_tiempo=$(date +%s%N)
temp_mov=0
temp_anim=0

while true;do
    ahora=$(date +%s%N)
    delta=$((ahora - ultimo_tiempo))
    ultimo_tiempo=$ahora
    
    # LÓGICA DE MOVIMIENTO (ESTADO 0).
    if [[ $estado_juego -eq 0 ]];then
        temp_mov=$((temp_mov + delta))
        
        if [[ $temp_mov -ge $vel_movimiento ]];then
            pac_x=$((pac_x + pac_dir))
            actualizar_mascara

            # Límites zigzag.
            if [[ $pac_dir -eq -1 ]] && [[ $pac_x -le $((offset_x - limite_pacman_izq)) ]];then
                pac_dir=1;pac_y=$((pac_y - 4));pac_x=$((offset_x - limite_pacman_izq))
            fi
            
            if [[ $pac_dir -eq 1 ]] && [[ $pac_x -ge $((offset_x + ancho_planeta + limite_pacman_der)) ]];then
                pac_dir=-1;pac_y=$((pac_y - 4));pac_x=$((offset_x + ancho_planeta + limite_pacman_der))
            fi
            
            # FIN DEL RECORRIDO (Salida del script).
            if [[ $pac_y -le $((offset_y + 2)) ]] && [[ $pac_dir -eq 1 ]] && [[ $pac_x -ge $((offset_x + ancho_planeta)) ]];then
                 tput cnorm;echo -e "\e[H\e[2J\e[0m";exit 0
            fi
            
            temp_mov=0
            dibujar_escena
        fi
    fi
    
    # Acumular tiempo para la animación.
    temp_anim=$((temp_anim + delta))

    # Si pasó el tiempo suficiente, cambiar estado (0 a 1, o 1 a 0, etc).
    if [[ $temp_anim -ge $vel_anim ]];then
        estado_pac=$((1 - estado_pac))
        temp_anim=0
    fi
    
    sleep 0.001 # Descanso de CPU para no recargarla innecesariamente.
done

Análisis detallado: cómo funciona el motor de borrado del planeta y revelado del logotipo.

Antes de ejecutar el código extraído en la sección anterior, es imperativo aclarar un requisito físico. Aunque la lógica del script es autónoma, sus dependencias son los archivos de gráficos ASCII. Para que el programa funcione y no acuse errores del tipo "falta el directorio arte_ascii, o falta el archivo pacman", debes tener una carpeta llamada arte_ascii en el mismo directorio en donde guardes el script. Dentro de ella, deben existir los siguientes archivos de texto plano que contienen los dibujos: planeta, logo_entropía, pacman_cerrado, pacman_abierto_izq y pacman_abierto_der. Sin estos archivos, las matrices de memoria estarán vacías y el script no tendrá nada que dibujar.

Una vez cubierto este requisito, pasemos al análisis técnico de cómo BASH, un lenguaje pensado para administrar sistemas, es llevado aquí a comportarse como un excelente motor de videojuegos.

1. La carga de recursos por referencia.

El script comienza definiendo colores y variables, pero la primera pieza de ingeniería interesante es la función cargar_sprite. En lugar de escribir el código de carga cinco veces (una para cada archivo), se utiliza una función general. Lo notable aquí es el uso de local -n. Esto crea una "nameref" (referencia por nombre), que actúa como un puntero. Cuando llamamos a cargar_sprite "planeta" arte_planeta ..., la función no recibe una copia del array, sino que apunta directamente a la variable global arte_planeta. El comando mapfile -t es el encargado de leer el archivo de texto línea por línea y volcarlo directamente dentro del array. Sin este comando, tendríamos que utilizar, por ejemplo, bucles while read que son mucho más lentos.

2. El sistema de coordenadas relativas.

Para centrar el planeta en la pantalla, el script calcula el offset_y y offset_x basándose en el tamaño actual de la terminal (tput cols). Sin embargo, el reto matemático está en el logotipo. El logo de "Entropía binaria" es algo más pequeño que el planeta y debe aparecer centrado dentro de él. Para esto se calculan logo_rel_y y logo_rel_x. Estas variables almacenan la diferencia de posición entre la esquina superior izquierda del planeta y la esquina superior izquierda del logo. Este cálculo es fundamental para que más adelante sepamos exactamente qué píxel del logo corresponde a qué píxel del planeta.

3. El corazón del efecto: declare -A visitados.

Esta línea es muy importante, porque al declarar visitados como un array asociativo (-A), estamos creando una memoria no dependiente de índices numéricos consecutivos (0, 1, 2...), sino de claves de texto. El script utiliza este array como una "máscara de recorte". No guardamos el dibujo final, sino simplemente un registro de coordenadas "Y,X" por las que el Pacman ha pasado. Si la clave "5,10" existe en el array, significa que esa celda ya fue "comida".

4. La función actualizar_mascara.

Cada vez que el Pacman se mueve un paso, se llama a esta función. Su trabajo no es dibujar, sino calcular colisiones. El script recorre el cuadrado pequeño que forma el cuerpo del Pacman. Si el Pacman tiene un píxel sólido en una posición, el script traduce esa posición local a la posición global del planeta. La línea visitados["$ly,$lx"]=1 es la que "marca" el territorio. Nótese que no borramos el planeta en este punto; simplemente anotamos en nuestra libreta virtual que esa coordenada específica ya ha sido visitada.

5. El renderizado condicional (dibujar_escena).

Aquí es en donde ocurre la magia visual. El script no dibuja el logo y luego le pega el "planeta comido parcialmente" encima: esto posiblemente causaría parpadeos que el ojo humano sería capaz de advertir. En su lugar, decide píxel por píxel qué debe mostrar, componiendo una imagen que será la que se espere ver (planeta mezclado con logo de manera inteligente y razonable. El bucle anidado recorre todas las filas (i) y columnas (j) del área del planeta. En cada iteración, se hace una pregunta crítica: if [[ "${visitados["$i,$j"]}" == "1" ]]; ...

- Si la respuesta es SÍ (1): significa que el Pacman ya pasó por ahí. El script ignora el array del planeta y mira el array arte_logo. Utilizando las coordenadas relativas calculadas al principio, extrae el color y el caracter del logo y lo añade al búfer.
/ Si la respuesta es NO (vacío): significa que el terreno está intacto. El script ignora el logo y extrae el carácter del array arte_planeta, aplicándole los colores de tierra o agua.

Este enfoque permite que el logo aparezca "detrás" del planeta a medida que este es borrado, creando una sensación de profundidad y capas.

6. El bucle de física y el movimiento zigzag.

Finalmente, el bucle while true maneja el tiempo. Se usa la técnica de "Delta tiempo" (ahora - ultimo_tiempo) para controlar la velocidad. Esto asegura que el juego corra a la misma velocidad independientemente de la potencia de la CPU que esté ejecutando el script, cosa que no sucede con "sleep". El movimiento en zigzag se logra monitoreando la variable pac_x.

Cuando pac_dir es -1 (izquierda) y se llega al limite_pacman_izq, el script cambia pac_dir a 1 (derecha) y resta 4 a pac_y.

Al restar a pac_y, el personaje sube visualmente porque en la terminal, la fila 0 está arriba. Este cambio de dirección y altura crea el patrón de barrido que permite limpiar todo el mapa automáticamente hasta que la coordenada Y del Pacman supera la parte superior del planeta, momento en el cual el script detecta el fin del juego y termina, así como termina este artículo :D

 

Generación procedimental de entornos en terminal: anatomía de un cielo estrellado en BASH.


Generación procedimental de entornos en terminal: anatomía de un cielo estrellado en BASH.

Análisis del script de presentación ASCII de los videos de Entropía binaria para 2026. 

Cuando nos enfrentamos al desafío de crear videojuegos o animaciones en la terminal utilizando BASH, uno de los primeros obstáculos es dar profundidad a la escena. Un fondo negro plano puede resultar aburrido y bidimensional, mientras que un fondo dinámico o vistoso siempre aporta vida y contexto. En el script analizado, la solución para crear una atmósfera espacial es la implementación de un sistema de partículas estáticas simulando un campo de estrellas. A diferencia de dibujar un simple archivo de texto como fondo, este método genera las estrellas matemáticamente, lo que significa que cada vez que ejecutamos el programa, el cielo es único. Para lograr esto, no podemos simplemente imprimir caracteres al azar en un bucle infinito; necesitamos persistencia de datos.

El primer concepto a asimilar es que, en una animación en donde la pantalla se refresca constantemente (se borra y se redibuja), las estrellas no pueden cambiar de lugar en cada fotograma. Si generáramos coordenadas aleatorias dentro del bucle de dibujo, el fondo parecería estática de televisión. Por lo tanto, el paso fundamental ocurre antes de que empiece el juego, en la fase de inicialización. El script utiliza lo que llamamos matrices paralelas ("arrays" paralelos). En lugar de tener un "objeto estrella", BASH nos permite crear varias listas: una para la posición X, otra para la posición Y, una tercera para el carácter que representa la estrella y una última para su color. De este modo, el índice 0 de todos los arrays define las propiedades de la primera estrella, el índice 1 las de la segunda, y así sucesivamente hasta llegar al número total definido en la variable num_estrellas.

Para distribuir estos puntos luminosos, necesitamos conocer los límites de nuestro lienzo (terminal). El script hace uso del comando tput para obtener dinámicamente el ancho (cols) y el alto (lines) de la terminal actual. Dentro de un bucle for, utilizamos la variable interna $RANDOM combinada con el operador módulo (%) sobre estas dimensiones. Esto nos devuelve un número entre 0 y el tamaño de la pantalla, garantizando que ninguna estrella se genere fuera del área visible. Es aquí en donde entra en juego la variedad visual. Un cielo realista no se compone solo de puntos idénticos. El código define dos matrices (arrays) de caracteres: uno de chars_comunes (como puntos y comas) y otro de chars_raros (como asteriscos o círculos). Mediante una probabilidad simple, definida en prob_estrella_rara, el script decide si la estrella actual será un simple punto de luz distante o un cuerpo celeste más brillante, asignándole además un color aleatorio de una paleta ANSI predefinida.

Una vez que tenemos los datos almacenados en memoria, el desafío técnico se desplaza al motor de renderizado, específicamente dentro de la función dibujar_escena. Aquí es donde la optimización se vuelve crítica. BASH no es, en principio, un lenguaje rápido para dibujar gráficos, por lo tanto, dibujar cientos de elementos innecesarios podría llegar a causar parpadeos. El script implementa una técnica de "enmascaramiento" o "culling" muy interesante. Antes de agregar una estrella al búfer de dibujo, verifica si sus coordenadas caen dentro del rectángulo que ocupa el planeta (o el logo que no se ve al principio, porque está posicionado por detrás del planeta Tierra). Si la coordenada X e Y de la estrella están dentro de los límites del objeto principal (offset_x y offset_y), el script ejecuta un continue y salta esa iteración. Esto evita que se dibujen estrellas por encima del planeta, creando la ilusión de que el cuerpo celeste está en primer plano y las estrellas en el fondo, sin necesidad de complicados cálculos de capas Z (profundidad).

Finalmente, la materialización de todo este cálculo matemático en la pantalla se realiza mediante códigos de escape ANSI. No se utiliza el comando echo línea por línea, ya que esto generaría un proceso demasiado lento. En su lugar, se construye una variable gigante llamada buffer. Para cada estrella válida, se concatena una secuencia de escape que mueve el cursor directamente a la posición deseada (\e[Y;XH), establece el color, imprime el caracter y resetea el color. Al final del ciclo de dibujo, se imprime todo el búfer de una sola vez. Este enfoque está basado en que en la programación de terminal, el control preciso del cursor y la gestión eficiente de cadenas de caracteres y del tiempo de dibujado en pantalla son las herramientas que transforman simples datos numéricos en un universo visualmente atractivo. BASH posee límites inexplorados e infravalorados en cuanto a sus capacidades de dibujo.

Disección del código: implementación práctica del campo estelar.

Para comprender verdaderamente cómo funciona el sistema, no hay nada mejor que aislar este mecanismo del resto del juego. A continuación se presenta el código fuente extraído directamente del script original, limpio de cualquier lógica ajena al cielo estrellado (como el Pacman o el planeta), pero manteniendo intacta la matemática y la sintaxis que generan las estrellas. Este fragmento es totalmente funcional: puedes copiarlo, guardarlo (por ejemplo) como "estrellas.sh", darle permisos de ejecución y verás el mismo fondo estelar que en el juego.


#!/bin/bash

# CONFIGURACIÓN.
num_estrellas=100
prob_estrella_rara=10
c_reset="\e[0m"

# DEFINICIÓN DE CARACTERES Y COLORES.
chars_comunes=("." "·" "," "°")
chars_raros=("*" "+" "o" "O" "ø" "●")
colores_estrellas=("\e[34m" "\e[36m" "\e[33m" "\e[37m" "\e[31m" "\e[90m")

# DECLARACIÓN DE MATRICES PARALELAS (ARRAYS PARALELOS).
declare -a estrellas_x estrellas_y estrellas_char estrellas_color

# FUNCIÓN DE INICIALIZACIÓN (lógica exacta del script original).
iniciar_estrellas() {
    local ancho_term=$(tput cols)
    local alto_term=$(tput lines)

    for ((i=0;i<num_estrellas;i++));do
        # Posición aleatoria.
        estrellas_x[i]=$((RANDOM % ancho_term))
        estrellas_y[i]=$((RANDOM % alto_term))

        # Elegir caracter (común vs raro).
        if [[ $((RANDOM % prob_estrella_rara)) -eq 0 ]];then
            # Estrella rara.
            local idx=$((RANDOM % ${#chars_raros[@]}))
            estrellas_char[i]="${chars_raros[$idx]}"
        else
            # Estrella común (punto, coma, etc).
            local idx=$((RANDOM % ${#chars_comunes[@]}))
            estrellas_char[i]="${chars_comunes[$idx]}"
        fi

        # Elegir color aleatorio.
        local c_idx=$((RANDOM % ${#colores_estrellas[@]}))
        estrellas_color[i]="${colores_estrellas[$c_idx]}"
    done
}

# PREPARACIÓN DE PANTALLA.
tput civis;clear

# EJECUCIÓN.
iniciar_estrellas

# RENDERIZADO (extraído del bucle de dibujo principal).
buffer=""
for ((s=0; s<num_estrellas; s++));do
    sx=${estrellas_x[s]}
    sy=${estrellas_y[s]}
    
    # Agregar al búfer de dibujo.
    buffer+="\e[${sy};${sx}H${estrellas_color[s]}${estrellas_char[s]}${c_reset}"
done 

echo -ne "$buffer"

# FINALIZAR (esperar tecla para salir y restaurar cursor).
read -n 1 -s
tput cnorm;clear

 

Explicación línea por línea.

El script comienza con el clásico #!/bin/bash, indicando al sistema qué intérprete usar. Inmediatamente después, se definen las variables de control: num_estrellas=100 establece la cantidad de partículas y prob_estrella_rara=10 dicta que hay una probabilidad de 1 entre 10 de que aparezca una estrella especial. c_reset es simplemente el código de escape para limpiar el color al final de cada impresión.

A continuación, vemos la definición de los "activos" gráficos. chars_comunes y chars_raros son listas (matrices, arrays) que contienen los "glifos" (símbolos visuales de caracteres) que veremos en pantalla. colores_estrellas almacena los códigos ANSI para azul, cian, amarillo, blanco, rojo y gris. Luego, declare -a inicializa cuatro matrices (arrays) vacías (estrellas_x, estrellas_y, etc.). Estos serán nuestros contenedores de datos, los cuales es de buen proceder declararlos explícitamente cuando se trabaja con estructuras indizadas en BASH, para evitar comportamientos inesperados.

Entramos en la función iniciar_estrellas. Las dos primeras líneas dentro de la función usan tput cols y tput lines. Esto es dinámico: pregunta al sistema el tamaño actual de la ventana en el momento de la ejecución. Luego inicia un bucle for que se repetirá 100 veces (el valor de num_estrellas).

Dentro del bucle, estrellas_x[i]=$((RANDOM % ancho_term)) genera la coordenada horizontal. Al usar el módulo (%) sobre el ancho de la terminal, obtenemos un valor que siempre caerá dentro de la pantalla. Lo mismo ocurre con la coordenada "Y". El bloque if siguiente decide el aspecto: genera un número al azar entre 0 y 9. De ser 0 (probabilidad del 10%), entra en el bloque de estrellas raras, elige un índice aleatorio basado en la longitud de la matriz ${#chars_raros[@]} y asigna ese carácter a estrellas_char[i]. Si no es 0, hace lo mismo pero con la matriz de caracteres comunes. Finalmente, se selecciona un color al azar de la lista de colores disponibles.

Al salir de la función, encontramos tput civis;clear. civis oculta el cursor parpadeante (esencial para que se vea estético) y clear limpia la pantalla. Llamamos a la función iniciar_estrellas para llenar las matrices con datos.

El bloque final es el motor de renderizado. Creamos una variable vacía llamada buffer. Recorremos nuevamente los 100 glifos (las 100 estrellas) con un bucle. Extraemos las coordenadas X e Y guardadas en memoria. La línea clave es buffer+="\e[${sy};${sx}H...". Aquí estamos concatenando una cadena de texto que contiene una instrucción para mover el cursor a la posición Y, X guardada, luego aplica el color guardado, imprime el caracter guardado y resetea el color.

Finalmente, echo -ne "$buffer" vuelca toda esa información acumulada en la pantalla de un solo golpe. El comando read -n 1 -s (el cual se puede escribir también, por ejemplo, "read -sn1") pausa el script esperando que pulses una tecla, y tput cnorm restaura el cursor a modo visible antes de salir para no dejar a la terminal sin su cursor.

Brevísimo manual para subir scripts BASH a Codeberg

Brevísimo manual para subir scripts BASH a Codeberg.

Este proceso asume que ya tenés a git instalado.
Ante cualquier error, consultá la última sección (NOTAS IMPORTANTES).

 

Paso 1: inicializar un repositorio git local.

  • Abrí tu terminal en el directorio en donde tengas el script y las carpetas con los sub-archivos del mismo.
  • Una vez ahí, inicializá un repositorio git local con "git init" (esto creará una carpeta oculta ".git" en tu directorio, la cual git utilizará para rastrear los cambios)

La salida que verás en la terminal luego de ejecutar "git init", será similar a esta:

hint: Usando 'master' como el nombre de la rama inicial. Este nombre de rama predeterminado
hint: está sujeto a cambios. Para configurar el nombre de la rama inicial para usar en todos
hint: de sus nuevos repositorios, reprimiendo esta advertencia, llama a:
hint:
hint: git config --global init.defaultBranch <nombre>
hint:
hint: Los nombres comúnmente elegidos en lugar de 'master' son 'main', 'trunk' y
hint: 'development'. Se puede cambiar el nombre de la rama recién creada mediante este comando:
hint:
hint: git branch -m <nombre>
hint:
hint: Disable this message with "git config set advice.defaultBranchName false"

Paso 2: añadir tus archivos al repositorio.

Ahora, hay que decirle a git qué archivos querés que incluya en tu primer "paquete" de cambios. Vas a querer incluir el script junto con las carpetas del mismo, si es que las hay.

  • git add script.sh
  • git add carpeta/

Si tenés otros archivos importantes (licencia, archivos tipo "info", etc.), también agregalos utilizando el mismo procedimiento.

Si querés agregar todos los archivos nuevos y modificados en el directorio actual (cuidado con esto, asegurate de no incluir cosas que no querés), podés hacer "git add ." (el punto importa, y significa "todo lo que está acá").

Si quisieras subir casi todo, pero no todo (por ejemplo, podrías tener una carpeta llamada "respaldos" en donde fuiste guardando copias de tu proyecto, las cuales sería innecesario que luego quedaran en el repositorio de CodeBerg -es mi caso-), podés hacer esto:

- Creá un archivo llamado ".gitignore" en el directorio raíz del proyecto.
- Dentro del mismo, escribí el nombre exacto de los archivos y directorios que no quieras que sean subidos a CodeBerg. Guardalo.
- Ejecutá "git add ." y automáticamente se subirá todo al repositorio, excepto los archivos y carpetas cuyos nombres coincidan con exactitud con las líneas introducidas en ".gitignore".

NOTA: el uso de comodines (*) está soportado.

Paso 3: confirmar los cambios (commit).

Ahora, git ya sabe qué archivos añadir. Creá una "instantánea" de esos cambios con un mensaje que describa lo que hiciste. Esto se llama "commit".

  • git commit -m "Menú interactivo con barra en reversa."

El mensaje es importante para que vos (y otros) entiendan qué contiene ese commit.

Paso 4: crear un repositorio en Codeberg.

  • Ahora andá a la web de Codeberg en el navegador: https://codeberg.org/
  • Iniciá sesión.
  • Hacé clic en el botón "+" (arriba, a la derecha) y seleccioná "Nuevo repositorio".
     

  • Ponele un nombre representativo (ej: menu).
  • Podés añadir una descripción opcional.
  • Desmarcá la opción "Inicializar el repositorio" si vas a subir tus propios archivos "README". De no ser así, podés pedirle a CodeBerg que lo haga por vos, marcando esta opción.
  • Definí el tipo de licencia que quieras para tu proyecto. En nuestro caso, utilizamos "GPL 3.0 or later". Para que te aparezca esta opción, debés marcar la casilla "inicializar el repositorio".
  • Hacé clic en el botón azul "Crear repositorio".
  • Una vez creado, Codeberg te mostrará unas instrucciones. Buscá la sección que dice algo así como "enviar un repositorio existente desde la línea de comandos". Vas a necesitar las dos líneas de comando que te dará. Si CodeBerg simplemente crea el repositorio y no te brinda esta información, simplemente andá arriba, a la derecha, y copiá la línea "https://codeberg.org ... script.git".

Paso 5: conectar tu repositorio local con Codeberg.

  • Volvé a tu terminal.
  • Utilizá las dos líneas que te dio Codeberg (van a ser algo como esto, pero con tu nombre de usuario y el nombre de tu repo):

    git remote add origin https://codeberg.org/tu_usuario/tu_script.git

    git pull origin main --rebase

    git branch -m main

Acordate de que si no te dio esa información, podés obtenerla manualmente en el paso anterior (es la que figura en azul).
La primera línea (la azul) le dice a tu repositorio local dónde está el repositorio remoto en Codeberg.
La segunda, renombra tu rama principal a "main" (es la convención moderna).
La tercera, sincroniza los cambios iniciales (README/licencia).

Paso 6: subir tus cambios a Codeberg (push).

  • Finalmente, enviá todos tus cambios locales (el commit que hiciste) al repositorio en Codeberg con "git push -u origin main"

Te pedirá tu nombre de usuario y tu contraseña de Codeberg (o un token de acceso, si configuraste SSH).

¡Listo! Ahora, toca ir a la página de tu repositorio en Codeberg y ver que están todos tus archivos ahí, y que se actualizaron (o subieron) "hace 1 minuto".

NOTAS IMPORTANTES:

Si por alguna razón tenés que empezar de nuevo (te equivocaste, algo salió mal, etc.), tenés que borrar el directorio oculto ".git" que fue creado cuando hiciste "git init".

Si te "salta" este error en rojo

error: falló el empuje de algunas referencias a 'https://codeberg.org/tu_usuario/tu_script.git'
solucionalo así:

git pull --rebase origin main
git push origin main


Si en algún momento ves este mensaje:

Identidad del autor desconocido

*** Por favor cuéntame quién eres.

Ejecuta

git config --global user.email "you@example.com"
git config --global user.name "Tu Nombre"

para configurar la identidad por defecto de tu cuenta.
Omite --global para configurar tu identidad solo en este repositorio.

fatal: no es posible auto-detectar la dirección de correo (se obtuvo 'entropia@entropia-artix.(none)')
podés resolverlo ingresando las siguientes líneas en la terminal:

git config --global user.email "entropiabinaria38911@gmail.com"
git config --global user.name "Entropía binaria"

y volvé a repetir la línea que habías ejecutado sin éxito.
Esto generalmente sucede al procesar, por ejemplo:

git commit -m "Menú interactivo con barra en reversa."


Flag Counter Visitas previas a la existencia de este contador: 3433

Artículos aleatorios

    Páginas: