Cómo crear scripts de Bash utilizando variables externas y scripts integrados

Hay momentos en que un script necesita 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 scripts interactivos para resolver 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 registren las respuestas (incluidas las más confidenciales, como las contraseñas)
  • Leer datos de archivos de configuración usando otros programas
  • Permitir que el script ignore las preguntas si se establecen variables externas
  • Y como beneficio adicional, escriba una hermosa interfaz de usuario (UI) con diálogos basados ​​en texto

Comience con un pequeño script para conectarse a un escritorio remoto mediante RDP.

Índice

Estudio de caso: Conéctese a un servidor remoto usando 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?

Haz preguntas, aprende a leer

Entonces, para un primer intento, escribí (versión 1) un contenedor de shell alrededor de freerdp que solicita usuario, contraseña y máquina remota. usaré el bash control de reproducción integrado:

#!/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})"

EN read (líneas 7, 13) en una variable, solo dices leer variables. Para hacerlo más fácil de usar, vaya -p (mostrar un aviso personalizado) y -r (lea las barras invertidas si está escribiendo un error).

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

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

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

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

También en el tema de la reutilización de código: coloque la lógica para conectarse a un servidor remoto en un archivo separado en caso de que desee reutilizar parte de esa lógica en otras situaciones similares. Por lo tanto, la nueva biblioteca Se ve como esto:

#!/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 Envoltorio RDP, versión 2 del script original, es mucho más simple ahora:

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

Todavía hay margen de mejora, así que sigue leyendo.

Siempre dé a los usuarios una opción: variables externas y más programas externos

Suponga que usa su script para iniciar sesión en la misma máquina todos los días. Lo más probable es que no cambie su usuario remoto, máquina y única contraseña de vez en cuando. Entonces puede guardar todas estas configuraciones 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": "[email protected]",
    "title" : "Remote desktop settings"
}

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

Para aprovechar, cambie el biblioteca (v2) se parece a esto:

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

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

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

#!/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 está solicitando dos parámetros; solo la contraseña:

$ ./kodegeek_rdp2.sh 
Password for [email protected]

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

He aquí cómo escribir un guión interactivo con una herramienta simple 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 está conectando a la misma empresa), no pedirá la información cada vez.

Tenga en cuenta que el cuadro de diálogo no es el único jugador en la ciudad. Me gusta porque está ampliamente disponible y por su simplicidad.

A continuación es versión 3 del escenario Está muy comentado. Puede ver que Dialog está funcionando 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 "[email protected]" 
           --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 "[email protected]" 
           --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

Concluir

Era 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 utilizar la función integrada de Bash read comando para obtener información de sus usuarios.
  • Puede verificar si la información repetitiva ya está disponible para evitar la lectura del entorno.
  • No guarda contraseñas sin encriptar. MantenerPassXC y Saltar son excelentes herramientas 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 Diálogo y otras herramientas fácilmente disponibles para que esto suceda.
  • Valide siempre sus entradas y compruebe si hay errores.

Artículos de interés

Subir