GNU/Linux >> Tutoriales Linux >  >> Linux

Cómo crear scripts Bash usando variables externas y scripts incrustados

Hay momentos en que un script debe solicitar información que no se puede almacenar en un archivo de configuración o cuando la cantidad de opciones no le permitirá especificar todas las posibilidades. Bash es bastante bueno creando secuencias de comandos interactivas para abordar este tipo de problemas.

Idealmente, al final de este artículo, debería poder hacer lo siguiente:

  • Escriba pequeños programas que hagan preguntas al usuario y guarden las respuestas (incluidas las confidenciales, como las contraseñas)
  • Leer datos de archivos de configuración usando otros programas
  • Permita que la secuencia de comandos omita hacer preguntas si se definen variables externas
  • Y como beneficio adicional, escriba una buena interfaz de usuario (UI) con cuadros de diálogo de texto

Comience con una pequeña secuencia de comandos para conectarse a un escritorio remoto mediante el protocolo RDP.

[ También puede disfrutar leyendo: Uso de Bash para la automatización ]

Estudio de caso:conectarse a un servidor remoto mediante RDP

En Linux, hay muchos clientes RDP y uno muy bueno es freerdp. Una forma de llamarlo es pasar una larga línea de banderas (con nombres cortos confusos) como esta:

/usr/bin/xfreerdp /cert-ignore /sound:sys:alsa /f /u:REMOTE_USER /v:MACHINE /p:mynotsosecretpassword

¿Hay una mejor manera de hacer esto?

Hacer preguntas, aprender a leer

Entonces, para un primer intento, escribí (versión 1) un contenedor de shell alrededor de freerdp que solicita el usuario, la contraseña y la máquina remota. Usaré el comando de lectura integrado de Bash:

#!/bin/bash
# author Jose Vicente Nunez
# Do not use this script on a public computer.
tmp_file=$(/usr/bin/mktemp 2>/dev/null) || exit 100
trap '/bin/rm -f $tmp_file' QUIT EXIT INT
/bin/chmod go-wrx "${tmp_file}" > /dev/null 2>&1
read -r -p "Remote RPD user: " REMOTE_USER|| exit 100
test -z "$REMOTE_USER" && exit 100
read -r -s -p "Password for $REMOTE_USER: " PASSWD|| exit 100
test -z "$PASSWD" && exit 100
echo
echo > "$tmp_file"|| exit 100
read -r -p "Remote server: " MACHINE|| exit 100
test -z "$REMOTE_USER" && exit 100
/usr/bin/xfreerdp /cert-ignore /sound:sys:alsa /f /u:"$REMOTE_USER" /v:"${MACHINE}" /p:"(/bin/cat ${tmp_file})"

Para read (líneas 7, 13) en una variable, simplemente diga leer variable . Para hacerlo más amigable, pasa -p (muestra un aviso personalizado) y -r (lea las barras invertidas si comete un error tipográfico).

read también le permite suprimir los caracteres que escribe en la pantalla. La opción se llama -s modo (secreto) (línea 9).

Una cosa que me molesta es que cualquiera que haga un ps -ef puedo ver mi contraseña en la línea de comando; para evitar eso, lo guardo en un archivo y luego, usando una subcapa, lo leo cuando xfreerdp lo necesita. Además, para evitar dejar mi contraseña en el disco, la guardo en un archivo temporal, que me aseguro de que se elimine una vez que finalice el script o se elimine.

Pero aun así... este guión sigue haciendo algunas preguntas una y otra vez. ¿Hay alguna manera de hacerlo, bueno, más inteligente?

Puede guardar algunos de los valores predeterminados, como los servidores remotos, en un archivo de configuración. Si no proporciona ninguno, utilice la configuración predeterminada.

También en el tema de la reutilización de código:coloque la lógica de cómo conectarse a un servidor remoto en un archivo separado en caso de que desee reutilizar parte de esta lógica en otras situaciones similares. Así que la nueva biblioteca se ve así:

#!/bin/bash
# author Jose Vicente Nunez
# Common logic for RDP connectivity
function remote_rpd {
    local remote_user=$1
    local pfile=$2
    local machine=$3
    test -z "$remote_user" && exit 100
    test ! -f "$pfile" && exit 100
    test -z "$machine" && exit 100
    /usr/bin/xfreerdp /cert-ignore /sound:sys:alsa /f /u:"$remote_user" /v:"${machine}" /p:"(/bin/cat ${pfile})" && return 0|| return 1
}

El contenedor RDP, la versión 2 del script original, ahora es mucho más simple:

#!/bin/bash
# author Jose Vicente Nunez
# Do not use this script on a public computer.
# shellcheck source=/dev/null.
. "rdp_common.sh"
tmp_file=$(/usr/bin/mktemp 2>/dev/null) || exit 100
trap '/bin/rm -f $tmp_file' QUIT EXIT INT
/bin/chmod go-wrx "${tmp_file}" > /dev/null 2>&1
read -r -p "Remote RPD user: " REMOTE_USER|| exit 100
read -r -s -p "Password for $REMOTE_USER: " PASSWD|| exit 100
echo
echo "$PASSWD" > "$tmp_file"|| exit 100
read -r -p "Remote server: " MACHINE|| exit 100
remote_rpd "$REMOTE_USER" "$tmp_file" "$MACHINE"

Entonces, después de este cambio, ¿cómo se ve?

$ ./kodegeek_rdp2.sh
Remote RPD user: jose
Password for jose: 
Remote server: myremotemachine.kodegeek.com

Hay más espacio para mejorar, así que sigue leyendo.

Dé siempre una opción a los usuarios:Variables externas y más programas externos

Digamos que usa su script para conectarse a la misma máquina todos los días. Lo más probable es que no cambie su usuario remoto, máquina y solo la contraseña de vez en cuando. De modo que puedes guardar todos esos ajustes en un archivo de configuración, que solo puede leer el usuario actual y nadie más:

(Ejemplo de ~/.config/scripts/kodegeek_rdp.json )

{
    "machines": [
        {
            "name": "myremotemachine.kodegeek.com",
            "description": "Personal-PC"
        },
        {
            "name": "vmdesktop1.kodegeek.com",
            "description": "Virtual-Machine"
        }
    ],
    "remote_user": "jose@MYCOMPANY",
    "title" : "Remote desktop settings"
}

Sí, JSON no es el mejor formato para archivos de configuración, pero este es bastante pequeño. Además, tenga en cuenta que ahora puede almacenar más de una máquina remota (para simplificar, use solo la primera).

Para aprovecharlo, modifique la biblioteca (v2) para que se vea así:

#!/bin/bash
# author Jose Vicente Nunez
# Common logic for RDP connectivity
if [[ -x '/usr/bin/jq' ]] && [[ -f "$HOME/.config/scripts/kodegeek_rdp.json" ]]; then
    REMOTE_USER="$(/usr/bin/jq --compact-output --raw-output '.remote_user' "$HOME"/.config/scripts/kodegeek_rdp.json)"|| exit 100
    MACHINE="$(/usr/bin/jq --compact-output --raw-output '.machines[0]| join(",")' "$HOME"/.config/scripts/kodegeek_rdp.json)"|| exit 100
    export REMOTE_USER
    export MACHINE
fi


function remote_rpd {
    local remote_user=$1
    local pfile=$2
    local machine=$3
    test -z "$remote_user" && exit 100
    test ! -f "$pfile" && exit 100
    test -z "$machine" && exit 100
    /usr/bin/xfreerdp /cert-ignore /sound:sys:alsa /f /u:"$remote_user" /v:"${machine}" /p:"(/bin/cat ${pfile})" && return 0|| return 1
}

¿Notaste que no intenté leer la contraseña de un archivo de configuración? Esa es la única credencial que seguiré preguntando una y otra vez a menos que esté encriptada. El resto de los valores los obtienes usando jq, utilizando una subcapa.

Y, por supuesto, aquí hay una nueva versión (v3) del script:

#!/bin/bash
# author Jose Vicente Nunez
# Do not use this script on a public computer.
# shellcheck source=/dev/null
. "rdp_common2.sh" 
tmp_file=$(/usr/bin/mktemp 2>/dev/null) || exit 100
trap '/bin/rm -f $tmp_file' QUIT EXIT INT
/bin/chmod go-wrx "${tmp_file}" > /dev/null 2>&1
if [ -z "$REMOTE_USER" ]; then
    read -r -p "Remote RPD user: " REMOTE_USER|| exit 100
fi
read -r -s -p "Password for $REMOTE_USER: " PASSWD|| exit 100
echo
echo "$PASSWD" > "$tmp_file"|| exit 100
if [ -z "$MACHINE" ]; then
    read -r -p "Remote server: " MACHINE|| exit 100
fi
remote_rpd "$REMOTE_USER" "$tmp_file" "$MACHINE"

Tenga en cuenta que ya no solicita dos parámetros; solo la contraseña:

$ ./kodegeek_rdp2.sh 
Password for jose@MYCOMPANY: 

¿Hay algo más que pueda hacer para mejorar este script?

Quiero una buena interfaz de usuario de texto:nada como un buen diálogo

Aquí se explica cómo escribir un guión interactivo con una herramienta sencilla llamada Diálogo. Pide al usuario que elija entre un número variable de máquinas (según el archivo de configuración) y, por supuesto, la contraseña. Sin embargo, si el usuario remoto es el mismo para ambas máquinas (lo cual es normal si se conecta a la misma empresa), no pedirá la información cada vez.

Tenga en cuenta que Dialog no es el único jugador en la ciudad. Simplemente me gusta porque está ampliamente disponible y por su simplicidad.

A continuación se muestra la versión 3 del script. Está muy comentado. Puede ver que Dialog funciona leyendo variables o archivos para habilitar/deshabilitar opciones. Pruébelo y ejecute el script para ver cómo encaja cada parte:

#!/bin/bash
# author Jose Vicente Nunez
# Do not use this script on a public computer.
# https://invisible-island.net/dialog/
SCRIPT_NAME="$(/usr/bin/basename "$0")"
DATA_FILE="$HOME/.config/scripts/kodegeek_rdp.json"
test -f "$DATA_FILE"|| exit 100
: "${DIALOG_OK=0}"
: "${DIALOG_CANCEL=1}"
: "${DIALOG_HELP=2}"
: "${DIALOG_EXTRA=3}"
: "${DIALOG_ITEM_HELP=4}"
: "${DIALOG_ESC=255}"
tmp_file=$(/usr/bin/mktemp 2>/dev/null) || declare tmp_file=/tmp/test$$
trap '/bin/rm -f $tmp_file' QUIT EXIT INT
/bin/chmod go-wrx "${tmp_file}" > /dev/null 2>&1

TITLE=$(/usr/bin/jq --compact-output --raw-output '.title' "$DATA_FILE")|| exit 100
REMOTE_USER=$(/usr/bin/jq --compact-output --raw-output '.remote_user' "$DATA_FILE")|| exit 100

# Choose a machine
MACHINES=$(
    tmp_file2=$(/usr/bin/mktemp 2>/dev/null) || declare tmp_file2=/tmp/test$$
    /usr/bin/jq --compact-output --raw-output '.machines[]| join(",")' "$DATA_FILE" > $tmp_file2|| exit 100
    declare -i i=0
    while read -r line; do
        machine=$(echo "$line"| /usr/bin/cut -d',' -f1)|| exit 100
        desc=$(echo "$line"| /usr/bin/cut -d',' -f2)|| exit 100
        toggle=off
        if [ $i -eq 0 ]; then
            toggle=on
            ((i=i+1))
        fi
        echo "$machine" "$desc" "$toggle"
    done < "$tmp_file2"
    /bin/cp /dev/null $tmp_file2
) || exit 100
# shellcheck disable=SC2086
/usr/bin/dialog \
    --clear \
    --title "$TITLE" \
    --radiolist "Which machine do you want to use?" 20 61 2 \
    $MACHINES 2> ${tmp_file}
return_value=$?

case $return_value in
  "$DIALOG_OK")
    remote_machine="$(/bin/cat ${tmp_file})"
    ;;
  "$DIALOG_CANCEL")
    echo "Cancel pressed.";;
  "$DIALOG_HELP")
    echo "Help pressed.";;
  "$DIALOG_EXTRA")
    echo "Extra button pressed.";;
  "$DIALOG_ITEM_HELP")
    echo "Item-help button pressed.";;
  "$DIALOG_ESC")
    if test -s $tmp_file ; then
      /bin/rm -f $tmp_file
    else
      echo "ESC pressed."
    fi
    ;;
esac

if [ -z "${remote_machine}" ]; then
  /usr/bin/dialog \
      --clear  \
    --title "Error, no machine selected?" --clear "$@" \
           --msgbox "No machine was selected!. Will exit now..." 15 30
  exit 100
fi

# Ask for the password
/bin/rm -f ${tmp_file}
/usr/bin/dialog \
  --title "$TITLE" \
  --clear  \
  --insecure \
  --passwordbox "Please enter your remote password for ${remote_machine}\n" 16 51 2> $tmp_file
return_value=$?
passwd=$(/bin/cat ${tmp_file})
/bin/rm -f "$tmp_file"
if [ -z "${passwd}" ]; then
  /usr/bin/dialog \
      --clear  \
    --title "Error, empty password" --clear "$@" \
           --msgbox "Empty password!" 15 30
  exit 100
fi

# Try to connect
case $return_value in
  "$DIALOG_OK")
    /usr/bin/mkdir -p -v "$HOME"/logs
    /usr/bin/xfreerdp /cert-ignore /sound:sys:alsa /f /u:"$REMOTE_USER" /v:"${remote_machine}" /p:"${passwd}"| \
    /usr/bin/tee "$HOME"/logs/"$SCRIPT_NAME"-"$remote_machine".log
    ;;
  "$DIALOG_CANCEL")
    echo "Cancel pressed.";;
  "$DIALOG_HELP")
    echo "Help pressed.";;
  "$DIALOG_EXTRA")
    echo "Extra button pressed.";;
  "$DIALOG_ITEM_HELP")
    echo "Item-help button pressed.";;
  "$DIALOG_ESC")
    if test -s $tmp_file ; then
      /bin/rm -f $tmp_file
    else
      echo "ESC pressed."
    fi
    ;;
esac

[ ¿Empezando con los contenedores? Consulta este curso gratuito. Implementación de aplicaciones en contenedores:una descripción técnica general. ]

Resumir

Eso fue mucho terreno para cubrir en un artículo. Los scripts como el desarrollado en este artículo simplifican las conexiones y facilitan la interfaz para los usuarios. Estas son las cosas que aprendiste a hacer:

  • Puede usar el read incorporado de Bash comando para obtener información de sus usuarios.
  • Puede verificar si la información repetitiva ya está disponible para evitar la lectura del entorno.
  • Las contraseñas no se guardan sin encriptar. KeepPassXC y Vault son herramientas excelentes que puede usar para evitar codificar información confidencial en los lugares equivocados.
  • Desea una interfaz de usuario más agradable, por lo que puede usar Dialog y otras herramientas fácilmente disponibles para lograrlo.
  • Siempre valide sus entradas y compruebe si hay errores.

Linux
  1. Cómo identificar directorios de trabajo usando variables y caracteres de shell

  2. Usando Bash para la automatización

  3. Cómo establecer/crear variables de entorno y shell en Linux

  4. Cómo crear y llamar funciones en Bash

  5. Cómo crear y aplicar parches en GIT usando diff y aplicar el comando

Cómo crear cuadros de diálogo GUI en Bash Scripts con Whiptail en Linux

Cómo depurar scripts de Bash en Linux y Unix

Cómo crear y ejecutar un programa en C usando Ubuntu 20.04 LTS

Cómo crear documentos con scripts Bash

Cómo crear y administrar particiones de Linux usando Parted

VMware:cómo crear una máquina virtual e instalar un sistema operativo invitado mediante vSphere Client