A veces es necesario generar documentos de varias líneas con estructuras anidadas complejas, como YAML o HTML, desde scripts de Bash. Puede lograr esto usando algunas funciones especiales de Bash, como documentos aquí . Un "documento aquí" es un código o bloque de texto que se puede redirigir a un script o 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 matrices, diccionarios y contadores
- Trabajar con diferentes tipos de comentarios
- Generar documentos YAML y HTML
- Envíe correos electrónicos con texto y archivos adjuntos
[ Descargar ahora:una guía para administradores de sistemas sobre secuencias de comandos Bash. ]
Documentar un guión
Es importante comentar sus secuencias de comandos y puede crear comentarios de una sola línea con un #
, o puede tener comentarios de 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
Esta función de ayuda para su secuencia de comandos 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 de varias líneas es bastante útil por sí mismo, especialmente cuando se documentan guiones complejos. Sin embargo, hay un buen giro en el uso de documentos aquí que quizás hayas 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 pasaré a otra cosa para que puedas aplicar este conocimiento.
[ Para obtener más consejos sobre Bash, descargue esta Hoja de trucos de secuencias de comandos de Bash Shell]
Uso de matrices y diccionarios para generar un archivo YAML de inventario de Ansible
Supongamos que tiene el siguiente archivo CSV con una lista de hosts en cada línea que contiene servidores o escritorios:
# List of hosts, tagged by group
macmini2:servers
raspberrypi:servers
dmaf5:desktops
mac-pro-1-1:desktops
Desea convertir la lista al siguiente archivo de inventario YAML de Ansible:
---
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 (escritorios o servidores) tendrá una variable diferente llamada
description
. El uso de matrices y matrices y contadores asociativos le permite satisfacer este requisito. - La secuencia de comandos debería fallar si el usuario no proporciona todas las etiquetas correctas. Un inventario incompleto no es aceptable. Para este requisito, un simple contador ayudará.
Este script logra 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 "$@"; 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 "$@"; 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
Así es como se ve la salida:
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 dejar que el libro de jugadas de Ansible lo use. Para simplificar el ejemplo, no hice eso aquí.
Enviar correos electrónicos HTML con archivos adjuntos YAML
El último ejemplo le mostrará cómo canalizar 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
Luego puede llamar al script de correo:
$ ./html_mail.sh [email protected] hosts.yaml
Si todo sale como se esperaba, Thunderbird creará un correo electrónico como este:
Conclusión
En resumen, ha aprendido a:
- Utilice estructuras de datos más sofisticadas, como matrices y matrices asociativas, para generar documentos
- Utilice contadores para realizar un seguimiento de los eventos
- Utilice aquí documentos para crear documentos YAML, instrucciones de ayuda, HTML, etc.
- Envíe correos electrónicos con HTML y YAML
Bash está bien para generar documentos pequeños y sin complicaciones. 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 cuando se trata de la creación de documentos complejos.