GNU/Linux >> Tutoriels Linux >  >> Linux

Comment créer des scripts Bash à l'aide de variables externes et de scripts intégrés

Il y a des moments où un script doit demander des informations qui ne peuvent pas être stockées dans un fichier de configuration ou lorsque le nombre de choix ne vous permet pas de spécifier toutes les possibilités. Bash est assez bon pour créer des scripts interactifs pour résoudre ce genre de problèmes.

Idéalement, à la fin de cet article, vous devriez être en mesure de faire ce qui suit :

  • Écrire de petits programmes qui posent des questions à l'utilisateur et enregistrent les réponses (y compris les réponses sensibles, comme les mots de passe)
  • Lire les données des fichiers de configuration à l'aide d'autres programmes
  • Autoriser le script à ne pas poser de questions si des variables externes sont définies
  • Et en prime, écrivez une belle interface utilisateur (UI) avec des boîtes de dialogue textuelles

Commencez par un petit script pour vous connecter à un poste de travail distant à l'aide du protocole RDP.

[ Vous aimerez peut-être également lire : Utiliser Bash pour l'automatisation ]

Étude de cas :se connecter à un serveur distant à l'aide de RDP

Sous Linux, il existe de nombreux clients RDP, et un très bon est freerdp. Une façon de l'appeler est de passer une longue ligne de drapeaux (avec des noms courts déroutants) comme ceci :

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

Existe-t-il une meilleure façon de procéder ?

Poser des questions, apprendre à lire

Donc, pour un premier essai, j'ai écrit (version 1) un wrapper shell autour de freerdp qui demande l'utilisateur, le mot de passe et la machine distante. Je vais utiliser la commande de lecture intégrée 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})"

Pour read (lignes 7, 13) dans une variable, vous dites simplement lire la variable . Pour le rendre plus convivial, passez -p (afficher une invite personnalisée) et -r (lisez les barres obliques inverses si vous faites une faute de frappe).

read vous permet également de supprimer les caractères que vous écrivez à l'écran. L'option s'appelle -s mode (secret) (ligne 9).

Une chose qui me dérange est que quiconque fait un ps -ef peut voir mon mot de passe sur la ligne de commande ; pour éviter cela, je l'enregistre dans un fichier, puis, à l'aide d'un sous-shell, je le relis lorsque xfreerdp en a besoin. De plus, pour éviter de laisser traîner mon mot de passe sur le disque, je l'enregistre dans un fichier temporaire, dont je m'assure qu'il sera supprimé une fois le script terminé ou qu'il sera tué.

Mais quand même... ce script continue de poser des questions encore et encore. Existe-t-il un moyen de le rendre plus intelligent ?

Vous pouvez enregistrer certaines des valeurs par défaut, comme les serveurs distants, dans un fichier de configuration. Si vous n'en fournissez aucun, utilisez les paramètres par défaut.

Également sur le sujet de la réutilisation du code :placez la logique de connexion à un serveur distant dans un fichier séparé au cas où vous voudriez réutiliser une partie de cette logique dans d'autres situations similaires. La nouvelle bibliothèque ressemble donc à ceci :

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

Le wrapper RDP, version 2 du script original, est beaucoup plus simple maintenant :

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

Alors, après ce changement, à quoi ça ressemble ?

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

Il y a encore place à l'amélioration, alors continuez à lire.

Donnez toujours le choix aux utilisateurs :variables externes et autres programmes externes

Supposons que vous utilisez votre script pour vous connecter à la même machine tous les jours. Il y a de fortes chances que vous ne changiez pas votre utilisateur distant, votre machine et seulement le mot de passe de temps en temps. Vous pouvez donc enregistrer tous ces paramètres dans un fichier de configuration, lisible uniquement par l'utilisateur actuel et par personne d'autre :

(Exemple 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"
}

Oui, JSON n'est pas le meilleur format pour les fichiers de configuration, mais celui-ci est assez petit. Notez également que vous pouvez désormais stocker plusieurs machines distantes (pour plus de simplicité, n'utilisez que la première).

Pour en tirer parti, modifiez la bibliothèque (v2) pour qu'elle ressemble à ceci :

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

Avez-vous remarqué que je n'ai pas essayé de lire le mot de passe à partir d'un fichier de configuration ? C'est la seule information d'identification que je continuerai à demander encore et encore à moins qu'elle ne soit cryptée. Le reste des valeurs que vous obtenez en utilisant jq, en utilisant un sous-shell.

Et, bien sûr, voici une nouvelle version (v3) du 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"

Notez que vous ne demandez plus deux paramètres ; juste le mot de passe :

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

Pouvez-vous faire autre chose pour améliorer ce script ?

Je veux une belle interface utilisateur textuelle :rien de tel qu'un bon dialogue

Voici comment écrire un script interactif avec un outil simple appelé Dialog. Il demande à l'utilisateur de choisir entre un nombre variable de machines (selon le fichier de configuration) et, bien sûr, le mot de passe. Cependant, si l'utilisateur distant est le même pour les deux machines (ce qui est normal si vous vous connectez à la même entreprise), il ne demandera pas l'information à chaque fois.

Notez que Dialog n'est pas le seul joueur en ville. Il se trouve que je l'aime parce qu'il est largement disponible et en raison de sa simplicité.

Ci-dessous, la version 3 du script. C'est très commenté. Vous pouvez voir que Dialog fonctionne en lisant des variables ou des fichiers pour activer/désactiver les options. Essayez-le et exécutez le script pour voir comment chaque partie s'emboîte :

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

[ Vous débutez avec les conteneurs ? Découvrez ce cours gratuit. Déploiement d'applications conteneurisées :présentation technique. ]

Récapitulez

C'était beaucoup de terrain à couvrir dans un seul article. Des scripts comme celui développé dans cet article simplifient les connexions et facilitent l'interface pour les utilisateurs. Voici ce que vous avez appris à faire :

  • Vous pouvez utiliser le read intégré de Bash commande pour obtenir des informations de vos utilisateurs.
  • Vous pouvez vérifier si des informations répétitives sont déjà disponibles pour éviter la lecture de l'environnement.
  • Vous n'enregistrez pas les mots de passe sans chiffrement. KeepPassXC et Vault sont d'excellents outils que vous pouvez utiliser pour éviter de coder en dur des informations sensibles aux mauvais endroits.
  • Vous voulez une interface utilisateur plus agréable, afin de pouvoir utiliser Dialog et d'autres outils facilement disponibles pour y parvenir.
  • Validez toujours vos entrées et vérifiez les erreurs.

Linux
  1. Bash + Comment quitter le script secondaire et le script principal à chaque fois ?

  2. Comment créer et appliquer des correctifs dans GIT à l'aide de la commande diff et apply

  3. Comment tracer des scripts Python à l'aide de trace.py

  4. Comment écrire une chaîne de plusieurs lignes en utilisant Bash avec des variables ?

  5. Comment mettre à zéro les variables numériques dans zsh (et peut-être aussi bash ?)

Comment créer des boîtes de dialogue GUI dans des scripts Bash avec Whiptail sous Linux

Comment déboguer les scripts Bash sous Linux et Unix

Comment créer et exécuter un programme C avec Ubuntu 20.04 LTS

Comment créer des documents avec des scripts Bash

Comment créer et gérer des partitions Linux à l'aide de Parted

VMware :comment créer une machine virtuelle et installer un système d'exploitation invité à l'aide de vSphere Client