Cómo crear documentos con scripts Bash

[*]

A veces es necesario generar documentos de varias líneas con estructuras anidadas complejas, como YAML o HTML, a partir de scripts de Bash. Puede lograr esto usando algunas características especiales de Bash, como aquí los documentos. Un "documento aquí" es un código o un bloque de texto que se puede redirigir a un script o un programa interactivo. Esencialmente, una secuencia de comandos de Bash se convierte en un documento aquí cuando se redirige a otro comando, secuencia de comandos o programa interactivo.

Este artículo explica cómo:

  • Usar tablas, diccionarios y contadores
  • Trabajando con diferentes tipos de comentarios
  • Generar documentos YAML y HTML
  • Enviar correos electrónicos con texto y archivos adjuntos

[ Download now: A sysadmin's guide to Bash scripting. ]

Índice

Documentar un guión

Es importante comentar sus guiones, y puede crear comentarios de una sola línea con un #, o puede tener comentarios en varias líneas usando la combinación de : y <<ANYTAG.

Por ejemplo:

# This is a simple comment
: <<COMMENT

This is a multi-line comment
Very useful for some complex comments

COMMENT

Este función de ayuda para su script es otro ejemplo útil:

#!/bin/bash
SCRIPT=$(/usr/bin/basename $0)|| exit 100
export SCRIPT
function help_me {
    /usr/bin/cat<<EOF

$SCRIPT -- A cool script that names and oh wait...
------------------------------------------------------
$SCRIPT --arg1 $VALUE --arg2 $VALUE2

EOF

help_me
}

# To use the help function just call help
help_me

El formato multilínea es bastante útil por sí solo, especialmente cuando se documentan guiones complejos. Sin embargo, hay un buen giro en el uso de documentos aquí que quizás haya visto antes:

$ /usr/bin/cat<<EOF>$HOME/test_doc.txt
Here is a multi-line document that I want to save.
Note how I can use variables inside like HOME=$HOME.

EOF

Esto es lo que está escrito en el archivo:

$ /usr/bin/cat $HOME/test_doc.txt
Here is a multi-line document that I want to save.
Note how I can use variables inside like HOME=/home/josevnz.

Ahora seguiré adelante para que puedas aplicar este conocimiento.

[ For more Bash tips, download this Bash Shell Scripting Cheat Sheet ]

Uso de tablas y diccionarios para generar un archivo YAML de Ansible Inventory

Digamos que tienes lo siguiente Archivo CSV con una lista de hosts en cada línea que contiene servidores o estaciones de trabajo:

# List of hosts, tagged by group
macmini2:servers
raspberrypi:servers
dmaf5:desktops
mac-pro-1-1:desktops

Desea convertir la lista en el siguiente Ansible YAML archivo de inventario:

---
all:
  children:
    servers:
      hosts:
        macmini2:
        raspberrypi:
      vars:
        description: Linux servers for the Nunez family
    desktops:
      hosts:
        dmaf5:
        mac-pro-1-1:
      vars:
        description: Desktops for the Nunez family        

Restricciones adicionales:

  • Cada tipo de sistema (sobremesa o servidor) tendrá una variable distinta denominada description. Utilizando arrays y arrays asociativos y contadores le permite cumplir con este requisito.
  • El script debería fallar si el usuario no proporciona todas las etiquetas correctas. Un inventario incompleto no es aceptable. Para este requisito, un simple medidor te ayudará.

Este guión logró el objetivo:

#!/bin/bash
:<<DOC
Convert a file in the following format to Ansible YAML:
# List of hosts, tagged by group
macmini2:servers
raspberrypi:servers
dmaf5:desktops
mac-pro-1-1:desktops
DOC
SCRIPT="$(/usr/bin/basename "$0")"|| exit 100
function help {
    /usr/bin/cat<<EOF
Example:
$SCRIPT $HOME/inventory_file.csv servers desktops
EOF
}

# We could use a complicated if-then-else or a case ... esac 
# to handle the tag description logic
# with an Associate Array is very simple
declare -A var_by_tag
var_by_tag["desktops"]="Desktops for the Nunez family"
var_by_tag["servers"]="Linux servers for the Nunez family"

function extract_hosts {
    tag=$1
    host_file=$2
    /usr/bin/grep -P ":$tag$" "$host_file"| /usr/bin/cut -f1 -d':'
    test $? -eq 0 && return 0|| return 1
}
# Consume the host file
hosts_file=$1
shift 1
if [ -z "$hosts_file" ]; then
    echo "ERROR: Missing host file!"
    help
    exit 100
fi

if [ ! -f "$hosts_file" ]; then
    echo "ERROR: Cannot use provided host file: $hosts_file"
    help
    exit 100
fi
# Consume the tags
if [ -z "$*" ]; then
    echo "ERROR: You need to provide one or more tags for the script to work!"
    help
    exit 100
fi
: <<DOC
Generate the YAML
The most annoying part is to make sure the indentation is correct. YAML depends entirely on proper indentation.
The idea is to iterate through the tags and perform the proper actions based on each.
DOC
for tag in "[email protected]"; do # Quick check for tag description handling. Show the user available tags if that happens
    if [ -z "${var_by_tag[$tag]}" ]; then
        echo "ERROR: I don't know how to handle tag=$tag (known tags=${!var_by_tag[*]}). Fix the script!"
        exit 100
    fi
done
/usr/bin/cat<<YAML
---
all:
  children:
YAML
# I do want to split by spaces to initialize my array, this is OK:
# shellcheck disable=SC2207
for tag in "[email protected]"; do
    /usr/bin/cat<<YAML
    $tag:
      hosts:
YAML
    declare -a hosts=($(extract_hosts "$tag" "$hosts_file"))|| exit 100
    host_cnt=0 # Declare your counter
    for host in "${hosts[@]}"; do
        /usr/bin/cat<<YAML
        $host:
YAML
        ((host_cnt+=1)) # This is how you increment a counter
    done
    if [ "$host_cnt" -lt 1 ]; then
        echo "ERROR: Could not find a single host with tag=$tag"
        exit 100
    fi
    /usr/bin/cat<<YAML
      vars:
        description: ${var_by_tag[$tag]}
YAML
done

Esto es lo que salir se parece a:

all:
  children:
    servers:
      hosts:
        macmini2:
        raspberrypi:
      vars:
        description: Linux servers for the Nunez family
    desktops:
      hosts:
        dmaf5:
        mac-pro-1-1:
      vars:
        description: Desktops for the Nunez family

Una mejor manera podría ser crear un inventario dinámico y deje que el libro de jugadas de Ansible use esta. Para simplificar el ejemplo, no lo hice aquí.

Envío de correos electrónicos HTML con archivos adjuntos YAML

El último ejemplo le mostrará cómo redirigir un documento aquí a Mozilla Thunderbird (puede hacer algo similar con /usr/bin/mailx) para crear un mensaje con un documento HTML y archivos adjuntos:

#!/bin/bash
:<<HELP
Please take a look a the following document so you understand the Thunderbird command line below:
http://kb.mozillazine.org/Command_line_arguments_-_Thunderbird
HELP
declare EMAIL
EMAIL=$1
test -n "$EMAIL"|| exit 100
declare ATTACHMENT
test -n "$2"|| exit 100
test -f "$2"|| exit 100
ATTACHMENT="$(/usr/bin/realpath "$2")"|| exit 100
declare DATE
declare TIME
declare USER
declare KERNEL_VERSION
DATE=$(/usr/bin/date '+%Y%m%d')|| exit 100
TIME=$(/usr/bin/date '+%H:%M:%s')|| exit 100
USER=$(/usr/bin/id --real --user --name)|| exit 100
KERNEL_VERSION=$(/usr/bin/uname -a)|| exit 100

/usr/bin/cat<<EMAIL| /usr/bin/thunderbird -compose "to='$EMAIL',subject="Example of here documents with Bash",message="/dev/stdin",attachment="$ATTACHMENT""

<!DOCTYPE html>
<html>
<head>
<style>
table {
  font-family: arial, sans-serif;
  border-collapse: collapse;
  width: 100%;
}

td, th {
  border: 1px solid #dddddd;
  text-align: left;
  padding: 8px;
}

tr:nth-child(even) {
  background-color: #dddddd;
}
</style>
</head>
<body>
<h2>Hello,</p> <b>This is a public announcement from $USER:</h2>
<table>
  <tr>
    <th>Date</th>
    <th>Time</th>
    <th>Kernel version</th>
  </tr>
  <tr>
    <td>$DATE</td>
    <td>$TIME Rovelli</td>
    <td>$KERNEL_VERSION</td>
  </tr>
</table>
</body>
</html>
EMAIL

Entonces puedes llamar al guión de correo:

$ ./html_mail.sh [email protected] hosts.yaml

Si todo sale según lo planeado, Thunderbird creará un correo electrónico como este:

(José Vicente Núñez, CC BY-SA 4.0)

Envoltura

En resumen, has aprendido a:

  • Use estructuras de datos más sofisticadas como matrices y matrices asociativas para generar documentos
  • Use contadores para rastrear eventos
  • Use documentos aquí para crear documentos YAML, instrucciones de ayuda, HTML, etc.
  • Enviar correos electrónicos con HTML y YAML

Bash está bien para generar documentos pequeños y simples. Si se trata de documentos grandes o complejos, es mejor que utilice otro lenguaje de secuencias de comandos como Python o Perl para obtener los mismos resultados con menos esfuerzo. Además, nunca subestime la importancia de un depurador real al crear documentos complejos.

Artículos de interés

Subir