GNU/Linux >> Linux Esercitazione >  >> Linux

Come creare script Bash utilizzando variabili esterne e script incorporati

Ci sono momenti in cui uno script deve richiedere informazioni che non possono essere archiviate in un file di configurazione o quando il numero di scelte non consente di specificare tutte le possibilità. Bash è piuttosto bravo a creare script interattivi per risolvere questo tipo di problemi.

Idealmente, entro la fine di questo articolo, dovresti essere in grado di eseguire le seguenti operazioni:

  • Scrivi piccoli programmi che pongono domande all'utente e salva le risposte (comprese quelle sensibili, come le password)
  • Legge i dati dai file di configurazione utilizzando altri programmi
  • Consenti allo script di saltare le domande se sono definite variabili esterne
  • E come bonus, scrivi una bella interfaccia utente (UI) con finestre di dialogo di testo

Inizia con un piccolo script per connetterti a un desktop remoto utilizzando il protocollo RDP.

[ Potresti anche divertirti a leggere: Utilizzo di Bash per l'automazione ]

Case study:connessione a un server remoto tramite RDP

Su Linux, ci sono molti client RDP e uno davvero buono è freerdp. Un modo per chiamarlo è passare una lunga fila di flag (con nomi brevi confusi) in questo modo:

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

C'è un modo migliore per farlo?

Fare domande, imparare a leggere

Quindi, per un primo tentativo, ho scritto (versione 1) un wrapper di shell attorno a freerdp che richiede l'utente, la password e la macchina remota. Userò il comando di lettura integrato di 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})"

Per read (righe 7, 13) in una variabile, basta dire leggi variabile . Per renderlo più amichevole, passa -p (mostra un prompt personalizzato) e -r (leggi le barre rovesciate se commetti un errore di battitura).

read ti consente anche di sopprimere i caratteri che scrivi sullo schermo. L'opzione si chiama -s modalità (segreta) (riga 9).

Una cosa che mi infastidisce è che chiunque faccia un ps -ef posso vedere la mia password sulla riga di comando; per evitarlo, lo salvo in un file e poi, usando una subshell, lo rileggo quando xfreerdp ne ha bisogno. Inoltre, per evitare di lasciare la mia password in giro sul disco, la salvo in un file temporaneo, che mi assicuro venga rimosso una volta terminato lo script o terminato.

Ma ancora... questo script continua a fare alcune domande ancora e ancora. C'è un modo per renderlo, beh, più intelligente?

È possibile salvare alcune delle impostazioni predefinite, come i server remoti, in un file di configurazione. Se non ne fornisci nessuno, utilizza le impostazioni predefinite.

Anche sull'argomento del riutilizzo del codice:inserisci la logica su come connettersi a un server remoto in un file separato nel caso in cui desideri riutilizzare parte di questa logica in altre situazioni simili. Quindi la nuova libreria si presenta così:

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

Il wrapper RDP, versione 2 dello script originale, ora è molto più semplice:

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

Quindi, dopo questa modifica, come appare?

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

C'è più margine di miglioramento, quindi continua a leggere.

Offri sempre agli utenti una scelta:variabili esterne e più programmi esterni

Supponi di utilizzare il tuo script per connetterti alla stessa macchina ogni giorno. È probabile che non cambierai l'utente remoto, la macchina e solo la password una volta ogni tanto. Quindi puoi salvare tutte queste impostazioni in un file di configurazione, leggibile solo dall'utente corrente e da nessun altro:

(Esempio di ~/.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 non è il formato migliore per i file di configurazione, ma questo è piuttosto piccolo. Inoltre, nota che ora puoi archiviare più di una macchina remota (per semplicità, usa solo la prima).

Per trarne vantaggio, modifica la libreria (v2) in modo che assomigli a questa:

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

Hai notato che non ho provato a leggere la password da un file di configurazione? Questa è l'unica credenziale che continuerò a chiedere più e più volte a meno che non sia crittografata. Il resto dei valori che ottieni utilizzando jq, utilizzando una subshell.

E, naturalmente, ecco una nuova versione (v3) dello 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"

Nota che non chiedi più due parametri; solo la password:

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

C'è qualcos'altro che puoi fare per migliorare questo script?

Voglio una bella interfaccia utente di testo:niente come una buona finestra di dialogo

Ecco come scrivere uno script interattivo con un semplice strumento chiamato Dialog. Chiede all'utente di scegliere tra un numero variabile di macchine (a seconda del file di configurazione) e, ovviamente, la password. Tuttavia, se l'utente remoto è lo stesso per entrambe le macchine (cosa normale se ci si connette alla stessa azienda), non chiederà le informazioni ogni volta.

Nota che Dialog non è l'unico giocatore in città. Mi piace solo perché è ampiamente disponibile e per la sua semplicità.

Di seguito è riportata la versione 3 dello script. È pesantemente commentato. Puoi vedere che la finestra di dialogo funziona leggendo variabili o file per abilitare/disabilitare le opzioni. Fai un tentativo ed esegui lo script per vedere come ogni parte si adatta insieme:

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

[ Iniziare con i container? Dai un'occhiata a questo corso gratuito. Distribuzione di applicazioni containerizzate:una panoramica tecnica. ]

Concludi

C'era molto terreno da coprire in un articolo. Script come quello sviluppato in questo articolo semplificano le connessioni e rendono l'interfaccia più semplice per gli utenti. Ecco le cose che hai imparato a fare:

  • Puoi usare la read incorporata in Bash comando per ottenere informazioni dai tuoi utenti.
  • Puoi controllare se sono già disponibili informazioni ripetitive per evitare la lettura dall'ambiente.
  • Non si salvano le password senza crittografia. KeepPassXC e Vault sono strumenti eccellenti che puoi utilizzare per evitare l'hardcoding di informazioni sensibili nei posti sbagliati.
  • Vuoi un'interfaccia utente migliore, in modo da poter utilizzare Dialog e altri strumenti prontamente disponibili per realizzarla.
  • Convalida sempre i tuoi input e verifica la presenza di errori.

Linux
  1. Come identificare le directory di lavoro usando i caratteri e le variabili della shell

  2. Come impostare/creare variabili di ambiente e shell in Linux

  3. Come creare e chiamare funzioni in Bash

  4. Come creare e applicare patch in GIT usando diff e applicare Command

  5. Come tracciare gli script Python usando trace.py

Come creare finestre di dialogo della GUI negli script Bash con Whiptail in Linux

Come eseguire il debug di script Bash in Linux e Unix

Come creare ed eseguire un programma C usando Ubuntu 20.04 LTS

Come creare documenti con gli script Bash

Come creare e gestire partizioni Linux utilizzando Parted

VMware:come creare una macchina virtuale e installare il sistema operativo guest utilizzando vSphere Client